Skip to content

Commit 8341a05

Browse files
committed
feat(setting): change titlebar icons
1 parent f1bd8aa commit 8341a05

File tree

13 files changed

+198
-91
lines changed

13 files changed

+198
-91
lines changed

backend/handlers/events/app/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import "./close";
22
import "./maximize";
33
import "./minimize";
4+
import "./openExternal";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { shell } from "electron";
2+
import { registerEvent } from "../utils/registerEvent";
3+
4+
const minimize = async (_event: Electron.IpcMainInvokeEvent, url: string) => {
5+
try {
6+
return await shell.openExternal(url);
7+
} catch (error) {
8+
console.error(error);
9+
return false;
10+
}
11+
};
12+
13+
registerEvent("openExternal", minimize);

backend/main.ts

+6-64
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
import {
2-
app,
3-
BrowserWindow,
4-
ipcMain,
5-
net,
6-
protocol,
7-
screen,
8-
shell,
9-
} from "electron";
1+
import { app, BrowserWindow, net, protocol } from "electron";
102
import path from "node:path";
113
import url, { fileURLToPath } from "node:url";
4+
import window from "./utils/window";
125

6+
export const __dirname = path.dirname(fileURLToPath(import.meta.url));
137
export let win: BrowserWindow | null;
148

159
// DEEP LINKING
@@ -40,7 +34,7 @@ if (!gotTheLock) {
4034
});
4135

4236
app.whenReady().then(async () => {
43-
createWindow();
37+
win = window.createWindow();
4438
protocol.handle("local", (request) => {
4539
const filePath = request.url.slice("local:".length);
4640
return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString());
@@ -60,11 +54,8 @@ if (!gotTheLock) {
6054
});
6155
}
6256

63-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
64-
6557
process.env.APP_ROOT = path.join(__dirname, "..");
6658

67-
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - [email protected]
6859
export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
6960
export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
7061
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
@@ -73,52 +64,6 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
7364
? path.join(process.env.APP_ROOT, "public")
7465
: RENDERER_DIST;
7566

76-
/**
77-
* Updates an existing key-value pair in an object, or inserts a new pair
78-
* if the key does not already exist.
79-
*
80-
* @param {Record<string, unknown>} obj
81-
* @param {string} keyToChange
82-
* @param {*} value
83-
* @returns {void}
84-
*/
85-
86-
function createWindow() {
87-
const { width: screenWidth, height: screenHeight } =
88-
screen.getPrimaryDisplay().workAreaSize;
89-
90-
win = new BrowserWindow({
91-
icon: path.join(process.env.VITE_PUBLIC, "icon.png"),
92-
webPreferences: {
93-
preload: path.join(__dirname, "preload.mjs"),
94-
},
95-
autoHideMenuBar: true,
96-
minWidth: 1000,
97-
minHeight: 600,
98-
frame: false,
99-
100-
// Set the initial width and height based on available screen size
101-
width: Math.min(screenWidth * 0.8, 1000), // 80% of screen width, max 1000
102-
height: Math.min(screenHeight * 0.8, 600), // 80% of screen height, max 600
103-
104-
resizable: true,
105-
});
106-
107-
// if (!isDev()) {
108-
// win.removeMenu();
109-
// }
110-
111-
ipcMain.handle("openExternal", async (_e, url: string) => {
112-
await shell.openExternal(url);
113-
});
114-
115-
if (VITE_DEV_SERVER_URL) {
116-
win.loadURL(VITE_DEV_SERVER_URL);
117-
} else {
118-
win.loadFile(path.join(RENDERER_DIST, "index.html"));
119-
}
120-
}
121-
12267
// Quit when all windows are closed, except on macOS. There, it's common
12368
// for applications and their menu bar to stay active until the user quits
12469
// explicitly with Cmd + Q.
@@ -130,9 +75,6 @@ app.on("window-all-closed", () => {
13075
});
13176

13277
app.on("activate", () => {
133-
// On OS X it's common to re-create a window in the app when the
134-
// dock icon is clicked and there are no other windows open.
135-
if (BrowserWindow.getAllWindows().length === 0) {
136-
createWindow();
137-
}
78+
if (BrowserWindow.getAllWindows().length <= 0) return;
79+
win = window.createWindow();
13880
});

backend/utils/settings/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export const defaultSettings: SettingsConfig = {
1212
checkForPluginUpdatesOnStartup: true,
1313
minimizeToTray: true,
1414
useAccountsForDownloads: false,
15+
titleBarStyle: "icons",
1516
};

backend/utils/window.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { BrowserWindow, screen } from "electron";
2+
import path from "node:path";
3+
import { RENDERER_DIST, VITE_DEV_SERVER_URL, __dirname } from "../main";
4+
import { settings } from "./settings/settings";
5+
6+
class Window {
7+
screenWidth: number = 0;
8+
screenHeight: number = 0;
9+
10+
public createWindow() {
11+
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
12+
this.screenWidth = width;
13+
this.screenHeight = height;
14+
15+
const titleBarStyle = settings.get("titleBarStyle");
16+
const frame = titleBarStyle === "native" || titleBarStyle === "none";
17+
18+
const win = new BrowserWindow({
19+
icon: path.join(process.env.VITE_PUBLIC, "icon.png"),
20+
webPreferences: {
21+
preload: path.join(__dirname, "preload.mjs"),
22+
},
23+
autoHideMenuBar: true,
24+
minWidth: 1000,
25+
minHeight: 600,
26+
frame,
27+
width: Math.min(width * 0.8, 1000),
28+
height: Math.min(height * 0.8, 600),
29+
resizable: true,
30+
});
31+
32+
const loadURL =
33+
VITE_DEV_SERVER_URL || path.join(RENDERER_DIST, "index.html");
34+
win.loadURL(loadURL);
35+
36+
return win;
37+
}
38+
}
39+
40+
const window = new Window();
41+
42+
export default window;

src/@types/settings/types.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export interface SettingsConfig {
2-
theme: "system" | "light" | "dark";
2+
theme: SettingsTheme;
33
language: string;
44
downloadsPath: string;
55
autoUpdate: boolean;
@@ -9,4 +9,13 @@ export interface SettingsConfig {
99
checkForPluginUpdatesOnStartup: boolean;
1010
minimizeToTray: boolean;
1111
useAccountsForDownloads: boolean;
12+
titleBarStyle: SettingsTitleBarStyle;
1213
}
14+
15+
export type SettingsTheme = "system" | "light" | "dark";
16+
17+
export type SettingsTitleBarStyle =
18+
| "icons"
19+
| "traffic-lights"
20+
| "native"
21+
| "none";

src/components/titleBar/control.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const TitleBarControl = ({
1212
}: TitleBarControlProps) => {
1313
return (
1414
<button
15-
className="titlebar-button group cursor-pointer p-1 rounded-full outline-none transition-transform transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-muted-foreground"
15+
className="p-1 transition-transform transform rounded-full outline-none cursor-pointer titlebar-button group hover:scale-110 focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-muted-foreground"
1616
onClick={() => invoke(`app:${type}`)}
1717
aria-label={type}
1818
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { invoke } from "@/lib";
2+
import { HtmlHTMLAttributes, PropsWithChildren } from "react";
3+
4+
interface TitleBarControlProps extends HtmlHTMLAttributes<HTMLButtonElement> {
5+
type: "minimize" | "maximize" | "close";
6+
}
7+
8+
const TitleBarControlWithIcon = ({
9+
type,
10+
className,
11+
children,
12+
...props
13+
}: PropsWithChildren<TitleBarControlProps>) => {
14+
return (
15+
<button
16+
className="p-1 transition-transform transform rounded-full outline-none cursor-pointer titlebar-button group hover:scale-110 focus-visible:ring-2"
17+
onClick={() => invoke(`app:${type}`)}
18+
aria-label={type}
19+
{...props}
20+
>
21+
{children}
22+
</button>
23+
);
24+
};
25+
26+
export default TitleBarControlWithIcon;

src/components/titleBar/icons.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Maximize2, Minus, X } from "lucide-react";
2+
import TitleBarControlWithIcon from "./controlWithIcon";
3+
4+
const TitleBarIcons = () => {
5+
return (
6+
<>
7+
<TitleBarControlWithIcon type="minimize">
8+
<Minus
9+
size={22}
10+
className="transition-all group-hover:text-yellow-500 group-focus-visible:text-yellow-500"
11+
/>
12+
</TitleBarControlWithIcon>
13+
<TitleBarControlWithIcon type="maximize">
14+
<Maximize2
15+
size={16}
16+
className="group-hover:text-green-500 group-focus-visible:text-green-500"
17+
/>
18+
</TitleBarControlWithIcon>
19+
<TitleBarControlWithIcon type="close">
20+
<X
21+
size={22}
22+
className="group-hover:text-red-500 group-focus-visible:text-red-500"
23+
/>
24+
</TitleBarControlWithIcon>
25+
</>
26+
);
27+
};
28+
29+
export default TitleBarIcons;

src/components/titleBar/index.tsx

+19-18
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
import { useLanguageContext } from "@/contexts/I18N";
2-
import TitleBarControl from "./control";
2+
import { useSettings } from "@/hooks";
3+
import TitleBarIcons from "./icons";
4+
import TitleBarTrafficLights from "./traffic-lights";
35

46
const TitleBar = () => {
57
const { t } = useLanguageContext();
8+
const { settings } = useSettings();
9+
10+
const titleBarStyle = settings?.titleBarStyle;
11+
12+
if (titleBarStyle === "none") return null;
13+
if (titleBarStyle === "native") return null;
14+
615
return (
7-
<div className="fixed top-0 z-50 w-full h-8 bg-background border-b border-muted shadow-md flex items-center">
8-
<div className="flex flex-row w-full justify-between items-center">
16+
<div className="fixed top-0 z-50 flex items-center w-full h-8 border-b shadow-md bg-background border-muted">
17+
<div className="flex flex-row items-center justify-between w-full">
918
{/* Title */}
10-
<div id="titlebar" className="flex-1 h-full flex items-center pl-3">
11-
<h1 className="font-semibold text-lg text-foreground pointer-events-none select-none">
19+
<div id="titlebar" className="flex items-center flex-1 h-full pl-3">
20+
<h1 className="text-lg font-semibold pointer-events-none select-none text-foreground">
1221
{t("falkor")}
1322
</h1>
1423
</div>
15-
{/* Control Buttons */}
1624
<div className="flex gap-1 pr-3">
17-
<TitleBarControl
18-
className="fill-yellow-400 group-hover:fill-yellow-500 group-focus-visible:fill-yellow-500"
19-
type="minimize"
20-
/>
21-
<TitleBarControl
22-
className="fill-green-400 group-hover:fill-green-500 group-focus-visible:fill-green-500"
23-
type="maximize"
24-
/>
25-
<TitleBarControl
26-
className="fill-red-400 group-hover:fill-red-500 group-focus-visible:fill-red-500"
27-
type="close"
28-
/>
25+
{titleBarStyle === "icons" ? (
26+
<TitleBarIcons />
27+
) : (
28+
<TitleBarTrafficLights />
29+
)}
2930
</div>
3031
</div>
3132
</div>
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import TitleBarControl from "./control";
2+
3+
const TitleBarTrafficLights = () => {
4+
return (
5+
<>
6+
<TitleBarControl
7+
className="fill-yellow-400 group-hover:fill-yellow-500 group-focus-visible:fill-yellow-500"
8+
type="minimize"
9+
/>
10+
<TitleBarControl
11+
className="fill-green-400 group-hover:fill-green-500 group-focus-visible:fill-green-500"
12+
type="maximize"
13+
/>
14+
<TitleBarControl
15+
className="fill-red-400 group-hover:fill-red-500 group-focus-visible:fill-red-500"
16+
type="close"
17+
/>
18+
</>
19+
);
20+
};
21+
22+
export default TitleBarTrafficLights;

src/features/navigation/components/navbar.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1+
import { useSettings } from "@/hooks";
2+
import { cn } from "@/lib";
13
import NavBarBottom from "./containers/bottom";
24
import NavBarTop from "./containers/top";
35

46
const NavBar = () => {
7+
const { settings } = useSettings();
8+
9+
const titleBarStyle = settings?.titleBarStyle;
10+
511
return (
6-
<aside className="fixed inset-y-0 top-8 left-0 z-10 w-16 flex flex-col border-r bg-background">
7-
<nav className="flex flex-col items-center gap-4 px-2 py-4 h-full">
12+
<aside
13+
className={cn(
14+
"fixed inset-y-0 left-0 z-10 w-16 flex flex-col border-r bg-background",
15+
{
16+
"top-8": !["none", "native"].includes(titleBarStyle),
17+
}
18+
)}
19+
>
20+
<nav className="flex flex-col items-center h-full gap-4 px-2 py-4">
821
<NavBarTop />
9-
<div className="flex flex-col flex-1 gap-3 border-t border-b w-full"></div>
22+
<div className="flex flex-col flex-1 w-full gap-3 border-t border-b"></div>
1023
<NavBarBottom />
1124
</nav>
1225
</aside>

0 commit comments

Comments
 (0)