Skip to content

Commit 04a0d7e

Browse files
committed
Layout: Fix title bar layout
1 parent 1e3e703 commit 04a0d7e

File tree

9 files changed

+141
-64
lines changed

9 files changed

+141
-64
lines changed

app/main.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ function createWindow(): BrowserWindow {
2626
titleBarStyle: 'hidden',
2727
title: "TensorBlock Desktop",
2828
...(process.platform === 'linux' ? { icon: path.resolve("resources/app.asar.unpacked/dist/logos/favicon.256x256.png") } : {}),
29-
...(process.platform !== 'linux' ?
30-
{titleBarOverlay: {
31-
height: 29,
32-
color: 'rgb(243, 243, 243)'
33-
}} : {}),
3429
frame: false,
3530
fullscreenable: false,
3631
autoHideMenuBar: true,
@@ -97,6 +92,15 @@ function createWindow(): BrowserWindow {
9792
return win?.isMaximized();
9893
});
9994

95+
// Emit when window's maximized state changes
96+
win?.on('maximize', () => {
97+
win?.webContents.send('window-maximized-change', true);
98+
});
99+
100+
win?.on('unmaximize', () => {
101+
win?.webContents.send('window-maximized-change', false);
102+
});
103+
100104
// Get the local IP address
101105
ipcMain.handle('get-local-ip', () => {
102106
const nets = os.networkInterfaces();

app/preload.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { contextBridge, ipcRenderer } from 'electron';
1+
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
22

33
/**
44
* The preload script runs before the renderer process starts and has access to both
@@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld('electron', {
1515
minimize: () => ipcRenderer.invoke('minimize-window'),
1616
isMaximized: () => ipcRenderer.invoke('get-window-isMaximized'),
1717
closeApp: () => ipcRenderer.send('close-app'),
18+
onWindowMaximizedChange: (callback: (event: IpcRendererEvent, maximized: boolean) => void) => ipcRenderer.on('window-maximized-change', callback),
1819

1920
// System information
2021
getDeviceInfo: () => ipcRenderer.invoke('get-device-info'),

src/components/layout/MainLayout.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { ReactNode, useState } from 'react';
2-
import TitleBar from './TitleBar';
32
import Sidebar from './Sidebar';
43
import SettingsPage from '../../pages/SettingsPage';
54
import TopBar from './TopBar';
@@ -34,7 +33,11 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
3433

3534
return (
3635
<div className="flex flex-col h-screen bg-white">
37-
<TitleBar />
36+
{/* <TitleBar /> */}
37+
<TopBar
38+
onSelectModel={handleSelectModel}
39+
onOpenSettingsDialog={handleOpenSettingsDialog}
40+
/>
3841

3942
<div className="flex flex-1 overflow-hidden">
4043
<Sidebar
@@ -44,25 +47,21 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
4447

4548
<div className="flex flex-col flex-1">
4649
{/* Main content */}
47-
48-
<TopBar
49-
onSelectModel={handleSelectModel}
50-
onOpenSettingsDialog={handleOpenSettingsDialog}
51-
/>
5250

53-
<div className="flex-1 overflow-auto">
54-
{children}
55-
</div>
56-
51+
{showSettings ?
52+
<SettingsPage
53+
isOpen={showSettings}
54+
onClose={() => setShowSettings(false)}
55+
/>
56+
:
57+
<div className="flex-1 overflow-auto">
58+
{children}
59+
</div>
60+
}
5761
{/* <BottomBar loadedModels={loadedModels} /> */}
5862
</div>
5963
</div>
60-
61-
{/* Settings Modal */}
62-
<SettingsPage
63-
isOpen={showSettings}
64-
onClose={() => setShowSettings(false)}
65-
/>
64+
6665
</div>
6766
);
6867
};

src/components/layout/Sidebar.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react';
22
import { MessageSquare, Settings } from 'lucide-react';
3-
import tensorBlockLogo from '/logos/TensorBlock_logo_dark.svg';
43

54
interface SidebarProps {
65
activePage: string;
@@ -10,21 +9,7 @@ interface SidebarProps {
109
export const Sidebar: React.FC<SidebarProps> = ({ activePage, onChangePage }) => {
1110
return (
1211
<div className="w-[68px] h-full bg-[#f8f8f8] border-r border-gray-200 flex flex-col">
13-
{/* Logo area */}
14-
<div className="flex items-center justify-center h-16 border-b border-gray-200">
15-
<div className="flex items-center justify-center w-10 h-10">
16-
<img
17-
src={tensorBlockLogo}
18-
alt="TensorBlock Logo"
19-
className="w-8 h-8"
20-
onError={(e) => {
21-
const target = e.target as HTMLImageElement;
22-
target.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5" /><path d="M16 16h.01" /><path d="M8 16h.01" /><path d="M12 8v8" /></svg>';
23-
}}
24-
/>
25-
</div>
26-
</div>
27-
12+
2813
{/* Navigation buttons */}
2914
<div className="flex flex-col items-center flex-1 pt-2">
3015
<button

src/components/layout/TopBar.tsx

Lines changed: 104 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { ChevronDown, Cpu, Settings } from 'lucide-react';
1+
import { ChevronDown, Cpu, Minimize2, Minus, Settings, Square, X } from 'lucide-react';
22
import React, { useEffect, useState } from 'react';
33
import { SelectModelDialog } from '../models/SelectModelDialog';
44
import { AIService, ModelOption } from '../../services/ai-service';
55
import { SettingsService } from '../../services/settings-service';
6+
import tensorBlockLogo from '/logos/TensorBlock_logo_dark.svg';
67

78
interface TopBarProps {
89
onSelectModel: (model: string, provider: string) => void;
@@ -14,6 +15,48 @@ const TopBar: React.FC<TopBarProps> = ({ onSelectModel, onOpenSettingsDialog })
1415
const [selectedModel, setSelectedModel] = useState('');
1516
const [selectedModelName, setSelectedModelName] = useState('');
1617
const [selectedProvider, setSelectedProvider] = useState('');
18+
const [isMaximized, setIsMaximized] = useState(false);
19+
20+
// Check if window is maximized on mount
21+
useEffect(() => {
22+
const checkMaximized = async () => {
23+
if (window.electron && window.electron.isMaximized) {
24+
const maximized = await window.electron.isMaximized();
25+
setIsMaximized(maximized);
26+
}
27+
};
28+
29+
checkMaximized();
30+
31+
window.electron.onWindowMaximizedChange((_event, maximized) => {
32+
setIsMaximized(maximized);
33+
});
34+
}, []);
35+
36+
// Window control handlers
37+
const handleMinimize = () => {
38+
if (window.electron && window.electron.minimize) {
39+
window.electron.minimize();
40+
}
41+
};
42+
43+
const handleMaximize = async () => {
44+
if (!window.electron) return;
45+
46+
if (isMaximized && window.electron.unmaximize) {
47+
await window.electron.unmaximize();
48+
setIsMaximized(false);
49+
} else if (window.electron.maximize) {
50+
await window.electron.maximize();
51+
setIsMaximized(true);
52+
}
53+
};
54+
55+
const handleClose = () => {
56+
if (window.electron && window.electron.closeApp) {
57+
window.electron.closeApp();
58+
}
59+
};
1760

1861
useEffect(() => {
1962
const settingsService = SettingsService.getInstance();
@@ -41,30 +84,70 @@ const TopBar: React.FC<TopBarProps> = ({ onSelectModel, onOpenSettingsDialog })
4184
};
4285

4386
return (
44-
<div className="flex items-center justify-center h-16 gap-8 px-6 bg-white border-b border-gray-200">
45-
<div className="flex items-center w-1/3 gap-2">
87+
<div className="flex items-center justify-between h-16 bg-white app-region-drag">
88+
{/* Logo area */}
89+
<div className="w-[68px] aspect-square flex items-center justify-center h-16">
90+
<div className="flex items-center justify-center w-10 h-10">
91+
<img
92+
src={tensorBlockLogo}
93+
alt="TensorBlock Logo"
94+
className="w-8 h-8"
95+
onError={(e) => {
96+
const target = e.target as HTMLImageElement;
97+
target.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5" /><path d="M16 16h.01" /><path d="M8 16h.01" /><path d="M12 8v8" /></svg>';
98+
}}
99+
/>
100+
</div>
101+
</div>
102+
103+
<div className='flex items-center justify-center gap-8 w-full'>
104+
<div className="flex items-center w-1/3 gap-2">
105+
<button
106+
className="btn hover:bg-gray-200 w-full bg-gray-100 border-0 rounded-md px-3 py-1.5 text-sm font-medium text-gray-700 flex justify-between app-region-no-drag"
107+
onClick={handleOpenModelDialog}
108+
aria-label="Select AI model"
109+
>
110+
<Cpu className="w-5 h-5 p-0.5 text-gray-600" />
111+
<span className='text-center truncate max-w-[200px]'>{selectedModelName}</span>
112+
<ChevronDown className="w-5 h-5 text-gray-600" />
113+
</button>
114+
</div>
115+
<button className="flex items-center justify-center gap-2 px-2 py-1 text-sm text-gray-600 border border-gray-200 rounded-md hover:text-gray-900 app-region-no-drag" onClick={onOpenSettingsDialog}>
116+
<Settings className="w-5 h-5 p-0.5" />
117+
<span className='truncate max-w-[200px]'>API</span>
118+
</button>
119+
120+
<SelectModelDialog
121+
isOpen={isModelDialogOpen}
122+
onClose={handleCloseModelDialog}
123+
onSelectModel={handleSelectModel}
124+
currentModelId={selectedModel}
125+
currentProviderId={selectedProvider}
126+
/>
127+
</div>
128+
129+
<div className='flex items-start justify-center gap-1 h-full'>
46130
<button
47-
className="btn hover:bg-gray-200 w-full bg-gray-100 border-0 rounded-md px-3 py-1.5 text-sm font-medium text-gray-700 flex justify-between"
48-
onClick={handleOpenModelDialog}
49-
aria-label="Select AI model"
131+
className='btn hover:bg-gray-200 bg-transparent border-0 px-3 py-1.5 text-sm font-medium text-gray-600 flex justify-center items-center app-region-no-drag'
132+
onClick={handleMinimize}
133+
>
134+
<Minus className='w-5 h-5' />
135+
</button>
136+
137+
<button
138+
className='btn hover:bg-gray-200 bg-transparent border-0 px-3 py-1.5 text-sm font-medium text-gray-600 flex justify-center items-center app-region-no-drag'
139+
onClick={handleMaximize}
140+
>
141+
{isMaximized ? <Minimize2 className='w-5 h-5' /> : <Square className='w-5 h-5 p-0.5' />}
142+
</button>
143+
144+
<button
145+
className='btn hover:bg-red-500 bg-transparent border-0 px-3 py-1.5 text-sm font-medium text-gray-600 hover:text-white flex justify-center items-center app-region-no-drag'
146+
onClick={handleClose}
50147
>
51-
<Cpu className="w-5 h-5 p-0.5 text-gray-600" />
52-
<span className='text-center truncate max-w-[200px]'>{selectedModelName}</span>
53-
<ChevronDown className="w-5 h-5 text-gray-600" />
148+
<X className='w-5 h-5' />
54149
</button>
55150
</div>
56-
<button className="flex items-center justify-center gap-2 px-2 py-1 text-sm text-gray-600 border border-gray-200 rounded-md hover:text-gray-900" onClick={onOpenSettingsDialog}>
57-
<Settings className="w-5 h-5 p-0.5" />
58-
<span className='truncate max-w-[200px]'>API</span>
59-
</button>
60-
61-
<SelectModelDialog
62-
isOpen={isModelDialogOpen}
63-
onClose={handleCloseModelDialog}
64-
onSelectModel={handleSelectModel}
65-
currentModelId={selectedModel}
66-
currentProviderId={selectedProvider}
67-
/>
68151
</div>
69152
);
70153
};

src/components/settings/ApiManagement.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ export const ApiManagement: React.FC<ApiManagementProps> = ({
425425
</div>
426426

427427
{/* Anthropic-specific settings */}
428-
{selectedProvider === 'Anthropic' && (
428+
{/* {selectedProvider === 'Anthropic' && (
429429
<div>
430430
<label className="block mb-2 text-sm font-medium text-gray-700">
431431
API Version
@@ -440,7 +440,7 @@ export const ApiManagement: React.FC<ApiManagementProps> = ({
440440
<option value="2023-01-01">2023-01-01</option>
441441
</select>
442442
</div>
443-
)}
443+
)} */}
444444

445445
{/* Custom provider settings */}
446446
{currentProviderSettings.customProvider && (

src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
@tailwind components;
33
@tailwind utilities;
44

5+
@theme{
6+
7+
}
8+
59
/* Add these styles for Electron window dragging */
610
.app-region-drag {
711
-webkit-app-region: drag;

src/pages/SettingsPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ export const SettingsPage: React.FC<SettingsPageProps> = ({
237237
if (!isOpen) return null;
238238

239239
return (
240-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white mt-[29px]">
241-
<div className="flex w-full h-full overflow-hidden bg-white rounded-lg shadow-xl">
240+
<div className="w-full h-full flex flex-1 items-center justify-center bg-white">
241+
<div className="flex w-full h-full overflow-hidden bg-white">
242242
{/* Sidebar */}
243243
<div className="flex flex-col w-64 h-full bg-gray-100 border-r border-gray-200">
244244
<div className="p-4 border-b border-gray-200">

src/types/window.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ interface Window {
1212
cpuUsage: number;
1313
}>;
1414
openUrl: (url: string) => Promise<void>;
15+
onWindowMaximizedChange: (callback: (event: IpcRendererEvent, maximized: boolean) => void) => void;
1516
};
1617
}

0 commit comments

Comments
 (0)