mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 03:31:24 +08:00
feat: support custom minimize, maximize and close (#9847)
* feat: add window control functionality for Windows and Linux - Introduced new IPC channels for window management: minimize, maximize, unmaximize, close, and check maximized state. - Implemented window control buttons in the UI, allowing users to minimize, maximize, and close the application. - Enhanced Navbar and TabContainer components to include window controls, improving user experience on non-Mac platforms. - Styled window control buttons for better visual integration. This update enhances the application's usability by providing essential window management features. * add tooltip * fix macos * lint error * update i18n * lint * fix: add WindowControls to MinApp popup and improve hover styles - Add WindowControls component to MinappPopupContainer title bar for Windows/Linux - Fix ButtonsGroup overlap with WindowControls by adding proper margin - Improve WindowControls hover background visibility by using rgba(128,128,128,0.3) - Ensure WindowControls is positioned at the right edge of title bar 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * lint * add types --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
19846c7e01
commit
b1a39e9b38
@ -123,6 +123,12 @@ export enum IpcChannel {
|
||||
Windows_SetMinimumSize = 'window:set-minimum-size',
|
||||
Windows_Resize = 'window:resize',
|
||||
Windows_GetSize = 'window:get-size',
|
||||
Windows_Minimize = 'window:minimize',
|
||||
Windows_Maximize = 'window:maximize',
|
||||
Windows_Unmaximize = 'window:unmaximize',
|
||||
Windows_Close = 'window:close',
|
||||
Windows_IsMaximized = 'window:is-maximized',
|
||||
Windows_MaximizedChanged = 'window:maximized-changed',
|
||||
|
||||
KnowledgeBase_Create = 'knowledge-base:create',
|
||||
KnowledgeBase_Reset = 'knowledge-base:reset',
|
||||
|
||||
@ -587,6 +587,41 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
return [width, height]
|
||||
})
|
||||
|
||||
// Window Controls
|
||||
ipcMain.handle(IpcChannel.Windows_Minimize, () => {
|
||||
checkMainWindow()
|
||||
mainWindow.minimize()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Windows_Maximize, () => {
|
||||
checkMainWindow()
|
||||
mainWindow.maximize()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Windows_Unmaximize, () => {
|
||||
checkMainWindow()
|
||||
mainWindow.unmaximize()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Windows_Close, () => {
|
||||
checkMainWindow()
|
||||
mainWindow.close()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Windows_IsMaximized, () => {
|
||||
checkMainWindow()
|
||||
return mainWindow.isMaximized()
|
||||
})
|
||||
|
||||
// Send maximized state changes to renderer
|
||||
mainWindow.on('maximize', () => {
|
||||
mainWindow.webContents.send(IpcChannel.Windows_MaximizedChanged, true)
|
||||
})
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
mainWindow.webContents.send(IpcChannel.Windows_MaximizedChanged, false)
|
||||
})
|
||||
|
||||
// VertexAI
|
||||
ipcMain.handle(IpcChannel.VertexAI_GetAuthHeaders, async (_, params) => {
|
||||
return vertexAIService.getAuthHeaders(params)
|
||||
|
||||
@ -66,11 +66,19 @@ export class WindowService {
|
||||
transparent: false,
|
||||
vibrancy: 'sidebar',
|
||||
visualEffectState: 'active',
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight,
|
||||
// For Windows and Linux, we use frameless window with custom controls
|
||||
// For Mac, we keep the native title bar style
|
||||
...(isMac
|
||||
? {
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight,
|
||||
trafficLightPosition: { x: 8, y: 13 }
|
||||
}
|
||||
: {
|
||||
frame: false // Frameless window for Windows and Linux
|
||||
}),
|
||||
backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF',
|
||||
darkTheme: nativeTheme.shouldUseDarkColors,
|
||||
trafficLightPosition: { x: 8, y: 13 },
|
||||
...(isLinux ? { icon } : {}),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
|
||||
@ -438,6 +438,20 @@ const api = {
|
||||
cherryin: {
|
||||
generateSignature: (params: { method: string; path: string; query: string; body: Record<string, any> }) =>
|
||||
ipcRenderer.invoke(IpcChannel.Cherryin_GetSignature, params)
|
||||
},
|
||||
windowControls: {
|
||||
minimize: (): Promise<void> => ipcRenderer.invoke(IpcChannel.Windows_Minimize),
|
||||
maximize: (): Promise<void> => ipcRenderer.invoke(IpcChannel.Windows_Maximize),
|
||||
unmaximize: (): Promise<void> => ipcRenderer.invoke(IpcChannel.Windows_Unmaximize),
|
||||
close: (): Promise<void> => ipcRenderer.invoke(IpcChannel.Windows_Close),
|
||||
isMaximized: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.Windows_IsMaximized),
|
||||
onMaximizedChange: (callback: (isMaximized: boolean) => void): (() => void) => {
|
||||
const channel = IpcChannel.Windows_MaximizedChanged
|
||||
ipcRenderer.on(channel, (_, isMaximized: boolean) => callback(isMaximized))
|
||||
return () => {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
ReloadOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { loggerService } from '@logger'
|
||||
import WindowControls from '@renderer/components/WindowControls'
|
||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||
import { useBridge } from '@renderer/hooks/useBridge'
|
||||
@ -434,7 +435,10 @@ const MinappPopupContainer: React.FC = () => {
|
||||
</Tooltip>
|
||||
)}
|
||||
<Spacer />
|
||||
<ButtonsGroup className={isWin || isLinux ? 'windows' : ''} isTopNavbar={isTopNavbar}>
|
||||
<ButtonsGroup
|
||||
className={isWin || isLinux ? 'windows' : ''}
|
||||
style={{ marginRight: isWin || isLinux ? '140px' : 0 }}
|
||||
isTopNavbar={isTopNavbar}>
|
||||
<Tooltip title={t('minapp.popup.goBack')} mouseEnterDelay={0.8} placement="bottom">
|
||||
<TitleButton onClick={() => handleGoBack(appInfo.id)}>
|
||||
<ArrowLeftOutlined />
|
||||
@ -500,6 +504,11 @@ const MinappPopupContainer: React.FC = () => {
|
||||
</TitleButton>
|
||||
</Tooltip>
|
||||
</ButtonsGroup>
|
||||
{(isWin || isLinux) && (
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, height: '100%' }}>
|
||||
<WindowControls />
|
||||
</div>
|
||||
)}
|
||||
</TitleContainer>
|
||||
)
|
||||
}
|
||||
@ -602,7 +611,6 @@ const ButtonsGroup = styled.div<{ isTopNavbar: boolean }>`
|
||||
gap: 5px;
|
||||
-webkit-app-region: no-drag;
|
||||
&.windows {
|
||||
margin-right: ${isWin ? '130px' : isLinux ? '100px' : 0};
|
||||
background-color: var(--color-background-mute);
|
||||
border-radius: 50px;
|
||||
padding: 0 3px;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { Sortable, useDndReorder } from '@renderer/components/dnd'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useFullscreen } from '@renderer/hooks/useFullscreen'
|
||||
@ -39,6 +39,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import MinAppIcon from '../Icons/MinAppIcon'
|
||||
import WindowControls from '../WindowControls'
|
||||
|
||||
interface TabsContainerProps {
|
||||
children: React.ReactNode
|
||||
@ -268,6 +269,7 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
|
||||
<SettingsButton onClick={handleSettingsClick} $active={activeTabId === 'settings'}>
|
||||
<Settings size={16} />
|
||||
</SettingsButton>
|
||||
<WindowControls />
|
||||
</RightButtonsContainer>
|
||||
</TabsBar>
|
||||
<TabContent>{children}</TabContent>
|
||||
@ -288,7 +290,7 @@ const TabsBar = styled.div<{ $isFullscreen: boolean }>`
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding-left: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? '75px' : '15px')};
|
||||
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWin ? '140px' : isLinux ? '120px' : '12px')};
|
||||
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : '0')};
|
||||
height: var(--navbar-height);
|
||||
position: relative;
|
||||
-webkit-app-region: drag;
|
||||
@ -427,6 +429,7 @@ const RightButtonsContainer = styled.div`
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: auto;
|
||||
padding-right: ${isMac ? '12px' : '0'};
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const WindowControlsContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
-webkit-app-region: no-drag;
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
export const ControlButton = styled.button<{ $isClose?: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 46px;
|
||||
height: var(--navbar-height);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => (props.$isClose ? '#e81123' : 'rgba(128, 128, 128, 0.3)')};
|
||||
color: ${(props) => (props.$isClose ? '#ffffff' : 'var(--color-text)')};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${(props) => (props.$isClose ? '#c50e1f' : 'rgba(128, 128, 128, 0.4)')};
|
||||
color: ${(props) => (props.$isClose ? '#ffffff' : 'var(--color-text)')};
|
||||
}
|
||||
|
||||
svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
`
|
||||
80
src/renderer/src/components/WindowControls/index.tsx
Normal file
80
src/renderer/src/components/WindowControls/index.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { isLinux, isWin } from '@renderer/config/constant'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Minus, Square, X } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { ControlButton, WindowControlsContainer } from './WindowControls.styled'
|
||||
|
||||
// Custom restore icon - two overlapping squares like Windows
|
||||
const RestoreIcon: React.FC<{ size?: number }> = ({ size = 14 }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1">
|
||||
{/* Back square (top-right) */}
|
||||
<path d="M 4 2 H 11 V 9 H 9 V 4 H 4 V 2" />
|
||||
{/* Front square (bottom-left) */}
|
||||
<rect x="2" y="4" width="7" height="7" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const WindowControls: React.FC = () => {
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
// Check initial maximized state
|
||||
window.api.windowControls.isMaximized().then(setIsMaximized)
|
||||
|
||||
// Listen for maximized state changes
|
||||
const unsubscribe = window.api.windowControls.onMaximizedChange(setIsMaximized)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Only show on Windows and Linux
|
||||
if (!isWin && !isLinux) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleMinimize = () => {
|
||||
window.api.windowControls.minimize()
|
||||
}
|
||||
|
||||
const handleMaximize = () => {
|
||||
if (isMaximized) {
|
||||
window.api.windowControls.unmaximize()
|
||||
} else {
|
||||
window.api.windowControls.maximize()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
window.api.windowControls.close()
|
||||
}
|
||||
|
||||
return (
|
||||
<WindowControlsContainer>
|
||||
<Tooltip title={t('navbar.window.minimize')} placement="bottom" mouseEnterDelay={0.2}>
|
||||
<ControlButton onClick={handleMinimize} aria-label="Minimize">
|
||||
<Minus size={14} />
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={isMaximized ? t('navbar.window.restore') : t('navbar.window.maximize')}
|
||||
placement="bottom"
|
||||
mouseEnterDelay={0.2}>
|
||||
<ControlButton onClick={handleMaximize} aria-label={isMaximized ? 'Restore' : 'Maximize'}>
|
||||
{isMaximized ? <RestoreIcon size={14} /> : <Square size={14} />}
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('navbar.window.close')} placement="bottom" mouseEnterDelay={0.2}>
|
||||
<ControlButton $isClose onClick={handleClose} aria-label="Close">
|
||||
<X size={17} />
|
||||
</ControlButton>
|
||||
</Tooltip>
|
||||
</WindowControlsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default WindowControls
|
||||
@ -6,6 +6,8 @@ import type { FC, PropsWithChildren } from 'react'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import WindowControls from '../WindowControls'
|
||||
|
||||
type Props = PropsWithChildren & HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const Navbar: FC<Props> = ({ children, ...props }) => {
|
||||
@ -28,7 +30,17 @@ export const NavbarLeft: FC<Props> = ({ children, ...props }) => {
|
||||
}
|
||||
|
||||
export const NavbarCenter: FC<Props> = ({ children, ...props }) => {
|
||||
return <NavbarCenterContainer {...props}>{children}</NavbarCenterContainer>
|
||||
return (
|
||||
<NavbarCenterContainer {...props}>
|
||||
{children}
|
||||
{/* Add WindowControls for Windows and Linux in NavbarCenter */}
|
||||
{(isWin || isLinux) && (
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, height: '100%', display: 'flex', alignItems: 'center' }}>
|
||||
<WindowControls />
|
||||
</div>
|
||||
)}
|
||||
</NavbarCenterContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export const NavbarRight: FC<Props> = ({ children, ...props }) => {
|
||||
@ -81,6 +93,7 @@ const NavbarCenterContainer = styled.div`
|
||||
padding: 0 ${isMac ? '20px' : 0};
|
||||
font-weight: bold;
|
||||
color: var(--color-text-1);
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const NavbarRightContainer = styled.div<{ $isFullscreen: boolean }>`
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Expand Dialog",
|
||||
"hide_sidebar": "Hide Sidebar",
|
||||
"show_sidebar": "Show Sidebar"
|
||||
"show_sidebar": "Show Sidebar",
|
||||
"window": {
|
||||
"close": "Close",
|
||||
"maximize": "Maximize",
|
||||
"minimize": "Minimize",
|
||||
"restore": "Restore"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Go to provider settings"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "伸缩对话框",
|
||||
"hide_sidebar": "隐藏侧边栏",
|
||||
"show_sidebar": "显示侧边栏"
|
||||
"show_sidebar": "显示侧边栏",
|
||||
"window": {
|
||||
"close": "关闭",
|
||||
"maximize": "最大化",
|
||||
"minimize": "最小化",
|
||||
"restore": "还原"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "跳转到服务商设置界面"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "伸縮對話框",
|
||||
"hide_sidebar": "隱藏側邊欄",
|
||||
"show_sidebar": "顯示側邊欄"
|
||||
"show_sidebar": "顯示側邊欄",
|
||||
"window": {
|
||||
"close": "關閉",
|
||||
"maximize": "最大化",
|
||||
"minimize": "最小化",
|
||||
"restore": "還原"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "跳轉到服務商設置界面"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Επισκευή διαλόγου",
|
||||
"hide_sidebar": "Απόκρυψη πλάγιας μπάρας",
|
||||
"show_sidebar": "Εμφάνιση πλάγιας μπάρας"
|
||||
"show_sidebar": "Εμφάνιση πλάγιας μπάρας",
|
||||
"window": {
|
||||
"close": "Κλείσιμο",
|
||||
"maximize": "Μεγιστοποίηση",
|
||||
"minimize": "Ελαχιστοποίηση",
|
||||
"restore": "Επαναφορά"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Μετάβαση στις ρυθμίσεις παρόχου"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Expandir cuadro de diálogo",
|
||||
"hide_sidebar": "Ocultar barra lateral",
|
||||
"show_sidebar": "Mostrar barra lateral"
|
||||
"show_sidebar": "Mostrar barra lateral",
|
||||
"window": {
|
||||
"close": "Cerrar",
|
||||
"maximize": "Maximizar",
|
||||
"minimize": "Minimizar",
|
||||
"restore": "Restaurar"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Ir a la configuración del proveedor"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Agrandir la boîte de dialogue",
|
||||
"hide_sidebar": "Cacher la barre latérale",
|
||||
"show_sidebar": "Afficher la barre latérale"
|
||||
"show_sidebar": "Afficher la barre latérale",
|
||||
"window": {
|
||||
"close": "Fermer",
|
||||
"maximize": "Agrandir",
|
||||
"minimize": "Réduire",
|
||||
"restore": "Restaurer"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Aller aux paramètres du fournisseur"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "ダイアログを展開",
|
||||
"hide_sidebar": "サイドバーを非表示",
|
||||
"show_sidebar": "サイドバーを表示"
|
||||
"show_sidebar": "サイドバーを表示",
|
||||
"window": {
|
||||
"close": "閉じる",
|
||||
"maximize": "最大化",
|
||||
"minimize": "最小化",
|
||||
"restore": "元に戻す"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "プロバイダー設定に移動"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Expandir caixa de diálogo",
|
||||
"hide_sidebar": "Ocultar barra lateral",
|
||||
"show_sidebar": "Mostrar barra lateral"
|
||||
"show_sidebar": "Mostrar barra lateral",
|
||||
"window": {
|
||||
"close": "Fechar",
|
||||
"maximize": "Maximizar",
|
||||
"minimize": "Minimizar",
|
||||
"restore": "Restaurar"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Ir para as configurações do provedor"
|
||||
|
||||
@ -1653,7 +1653,13 @@
|
||||
"navbar": {
|
||||
"expand": "Развернуть диалоговое окно",
|
||||
"hide_sidebar": "Скрыть боковую панель",
|
||||
"show_sidebar": "Показать боковую панель"
|
||||
"show_sidebar": "Показать боковую панель",
|
||||
"window": {
|
||||
"close": "Закрыть",
|
||||
"maximize": "Развернуть",
|
||||
"minimize": "Свернуть",
|
||||
"restore": "Восстановить"
|
||||
}
|
||||
},
|
||||
"navigate": {
|
||||
"provider_settings": "Перейти к настройкам поставщика"
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import WindowControls from '@renderer/components/WindowControls'
|
||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useFullscreen } from '@renderer/hooks/useFullscreen'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
@ -87,7 +88,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<NavbarRight style={{ justifyContent: 'space-between', flex: 1 }} className="home-navbar-right">
|
||||
<NavbarRight
|
||||
style={{ justifyContent: 'space-between', flex: 1, position: 'relative' }}
|
||||
className="home-navbar-right">
|
||||
<HStack alignItems="center">
|
||||
{!showAssistants && (
|
||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
||||
@ -114,18 +117,8 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</AnimatePresence>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</HStack>
|
||||
<HStack alignItems="center" gap={8}>
|
||||
<HStack alignItems="center" gap={6}>
|
||||
<UpdateAppButton />
|
||||
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
||||
<NarrowIcon onClick={() => SearchPopup.show()}>
|
||||
<Search size={18} />
|
||||
</NarrowIcon>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
|
||||
<NarrowIcon onClick={handleNarrowModeToggle}>
|
||||
<i className="iconfont icon-icon-adaptive-width"></i>
|
||||
</NarrowIcon>
|
||||
</Tooltip>
|
||||
{topicPosition === 'right' && !showTopics && (
|
||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}>
|
||||
<NavbarIcon onClick={toggleShowTopics}>
|
||||
@ -140,7 +133,47 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</NavbarIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
{/* For Mac, show search and expand without WindowControls */}
|
||||
{!isWin && !isLinux && (
|
||||
<>
|
||||
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
||||
<NarrowIcon onClick={() => SearchPopup.show()}>
|
||||
<Search size={18} />
|
||||
</NarrowIcon>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
|
||||
<NarrowIcon onClick={handleNarrowModeToggle}>
|
||||
<i className="iconfont icon-icon-adaptive-width"></i>
|
||||
</NarrowIcon>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
{/* Search, Expand and WindowControls positioned at the right edge */}
|
||||
{(isWin || isLinux) && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 6
|
||||
}}>
|
||||
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
||||
<NavbarIcon onClick={() => SearchPopup.show()}>
|
||||
<Search size={18} />
|
||||
</NavbarIcon>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
|
||||
<NavbarIcon onClick={handleNarrowModeToggle}>
|
||||
<i className="iconfont icon-icon-adaptive-width"></i>
|
||||
</NavbarIcon>
|
||||
</Tooltip>
|
||||
<WindowControls />
|
||||
</div>
|
||||
)}
|
||||
</NavbarRight>
|
||||
</Navbar>
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user