Squashed commit of the following:
commit d8c3e580c3026379fe94dc3ee26391e64a027fef
Author: Haritabh Singh <haritabh.z01+github@gmail.com>
Date: Tue Sep 16 21:52:24 2025 +0530
fix: make silent true for launched node process
commit 8cc95ea4d8b47763ec3018929b8fd6c505d5c506
Author: Haritabh Singh <haritabh.z01+github@gmail.com>
Date: Tue Sep 16 21:45:38 2025 +0530
chore: show traffic light spacing only on macs
commit ad4751cea8c3be36a93d327b109d68c50bb9a98b
Author: nchopra <naomi.chopra.021@gmail.com>
Date: Tue Sep 16 21:33:41 2025 +0530
feat: windows action buttons theme sync
This commit is contained in:
parent
c38d9eab1c
commit
c0d5403750
8 changed files with 161 additions and 44 deletions
|
|
@ -17,6 +17,13 @@ export function SiteHeader({ currentView }: SiteHeaderProps) {
|
|||
const router = useRouter();
|
||||
const [canGoBack, setCanGoBack] = useState(false);
|
||||
const [canGoForward, setCanGoForward] = useState(false);
|
||||
const [isMacOS, setIsMacOS] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Detect if running on macOS
|
||||
const platform = navigator.platform || navigator.userAgent;
|
||||
setIsMacOS(/Mac|Darwin/i.test(platform));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Track navigation history in session storage
|
||||
|
|
@ -136,7 +143,7 @@ export function SiteHeader({ currentView }: SiteHeaderProps) {
|
|||
>
|
||||
<div className="flex w-full items-center gap-1">
|
||||
{/* macOS traffic light button spacing */}
|
||||
<div className="w-[78px] flex-shrink-0" />
|
||||
{isMacOS && <div className="w-[78px] flex-shrink-0" />}
|
||||
|
||||
<div className="flex items-center gap-1 px-4 lg:gap-2 lg:px-6 py-1.5">
|
||||
<SidebarTrigger className="-ml-1" style={noDragRegion} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Moon, Sun, Monitor } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { api } from "@/trpc/react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -11,7 +12,16 @@ import {
|
|||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { setTheme, theme } = useTheme();
|
||||
const { setTheme } = useTheme();
|
||||
const updateUIThemeMutation = api.settings.updateUITheme.useMutation();
|
||||
|
||||
const handleThemeChange = (newTheme: "light" | "dark" | "system") => {
|
||||
// Update local theme immediately for instant feedback
|
||||
setTheme(newTheme);
|
||||
|
||||
// Sync with backend
|
||||
updateUIThemeMutation.mutate({ theme: newTheme });
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
|
|
@ -23,15 +33,15 @@ export function ThemeToggle() {
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
<DropdownMenuItem onClick={() => handleThemeChange("light")}>
|
||||
<Sun className="mr-2 h-4 w-4" />
|
||||
<span>Light</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
<DropdownMenuItem onClick={() => handleThemeChange("dark")}>
|
||||
<Moon className="mr-2 h-4 w-4" />
|
||||
<span>Dark</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
<DropdownMenuItem onClick={() => handleThemeChange("system")}>
|
||||
<Monitor className="mr-2 h-4 w-4" />
|
||||
<span>System</span>
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -39,25 +49,3 @@ export function ThemeToggle() {
|
|||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
export function ThemeToggleSimple() {
|
||||
const { setTheme, theme } = useTheme();
|
||||
|
||||
const toggleTheme = () => {
|
||||
if (theme === "light") {
|
||||
setTheme("dark");
|
||||
} else if (theme === "dark") {
|
||||
setTheme("system");
|
||||
} else {
|
||||
setTheme("light");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant="outline" size="icon" onClick={toggleTheme}>
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ const defaultSettings: AppSettingsData = {
|
|||
},
|
||||
ui: {
|
||||
theme: "system",
|
||||
sidebarOpen: false,
|
||||
currentView: "Voice Recording",
|
||||
},
|
||||
transcription: {
|
||||
language: "en",
|
||||
|
|
|
|||
|
|
@ -115,14 +115,6 @@ export interface AppSettingsData {
|
|||
};
|
||||
ui?: {
|
||||
theme: "light" | "dark" | "system";
|
||||
sidebarOpen?: boolean;
|
||||
currentView?: string;
|
||||
windowBounds?: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
transcription?: {
|
||||
language: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { BrowserWindow, screen, systemPreferences, app } from "electron";
|
||||
import {
|
||||
BrowserWindow,
|
||||
screen,
|
||||
systemPreferences,
|
||||
app,
|
||||
nativeTheme,
|
||||
} from "electron";
|
||||
import path from "node:path";
|
||||
import { logger } from "../logger";
|
||||
import { ServiceManager } from "../managers/service-manager";
|
||||
|
|
@ -16,22 +22,105 @@ export class WindowManager {
|
|||
private onboardingWindow: BrowserWindow | null = null;
|
||||
private widgetDisplayId: number | null = null;
|
||||
private cursorPollingInterval: NodeJS.Timeout | null = null;
|
||||
private themeListenerSetup: boolean = false;
|
||||
|
||||
createOrShowMainWindow(): void {
|
||||
private async getThemeColors(): Promise<{
|
||||
backgroundColor: string;
|
||||
symbolColor: string;
|
||||
}> {
|
||||
try {
|
||||
const settingsService =
|
||||
ServiceManager.getInstance()?.getService("settingsService");
|
||||
if (!settingsService) {
|
||||
// Default to light theme if service unavailable
|
||||
return { backgroundColor: "#ffffff", symbolColor: "#000000" };
|
||||
}
|
||||
|
||||
const uiSettings = await settingsService.getUISettings();
|
||||
const theme = uiSettings?.theme || "system";
|
||||
|
||||
// Determine if we should use dark colors
|
||||
let isDark = false;
|
||||
if (theme === "dark") {
|
||||
isDark = true;
|
||||
} else if (theme === "light") {
|
||||
isDark = false;
|
||||
} else if (theme === "system") {
|
||||
isDark = nativeTheme.shouldUseDarkColors;
|
||||
}
|
||||
|
||||
// Return appropriate colors
|
||||
return isDark
|
||||
? { backgroundColor: "#171717", symbolColor: "#fafafa" }
|
||||
: { backgroundColor: "#ffffff", symbolColor: "#171717" };
|
||||
} catch (error) {
|
||||
logger.main.error("Failed to get theme colors:", error);
|
||||
// Default to light theme on error
|
||||
return { backgroundColor: "#ffffff", symbolColor: "#000000" };
|
||||
}
|
||||
}
|
||||
|
||||
async updateAllWindowThemes(): Promise<void> {
|
||||
const colors = await this.getThemeColors();
|
||||
|
||||
// Update main window
|
||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
this.mainWindow.setTitleBarOverlay({
|
||||
color: colors.backgroundColor,
|
||||
symbolColor: colors.symbolColor,
|
||||
height: 32,
|
||||
});
|
||||
}
|
||||
|
||||
// Update onboarding window if it exists
|
||||
// Note: onboarding window has frame: false, so no title bar to update
|
||||
|
||||
logger.main.info("Updated window themes", colors);
|
||||
}
|
||||
|
||||
private setupThemeListener(): void {
|
||||
if (this.themeListenerSetup) return;
|
||||
|
||||
// Listen for system theme changes
|
||||
nativeTheme.on("updated", async () => {
|
||||
const settingsService =
|
||||
ServiceManager.getInstance()!.getService("settingsService")!;
|
||||
|
||||
const uiSettings = await settingsService.getUISettings();
|
||||
const theme = uiSettings?.theme || "system";
|
||||
|
||||
// Only update if theme is set to "system"
|
||||
if (theme === "system") {
|
||||
await this.updateAllWindowThemes();
|
||||
logger.main.info("System theme changed, updating windows");
|
||||
}
|
||||
});
|
||||
|
||||
this.themeListenerSetup = true;
|
||||
logger.main.info("Theme listener setup complete");
|
||||
}
|
||||
|
||||
async createOrShowMainWindow(): Promise<void> {
|
||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
this.mainWindow.show();
|
||||
this.mainWindow.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup theme listener on first window creation
|
||||
this.setupThemeListener();
|
||||
|
||||
// Get theme colors before creating window
|
||||
const colors = await this.getThemeColors();
|
||||
|
||||
this.mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
frame: true,
|
||||
titleBarStyle: "hidden",
|
||||
titleBarOverlay: {
|
||||
color: "#ffffff",
|
||||
symbolColor: "#000000",
|
||||
color: colors.backgroundColor,
|
||||
symbolColor: colors.symbolColor,
|
||||
height: 32,
|
||||
},
|
||||
trafficLightPosition: { x: 20, y: 16 },
|
||||
|
|
@ -170,6 +259,9 @@ export class WindowManager {
|
|||
return;
|
||||
}
|
||||
|
||||
// Setup theme listener if not already done
|
||||
this.setupThemeListener();
|
||||
|
||||
this.onboardingWindow = new BrowserWindow({
|
||||
width: 700,
|
||||
height: 600,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export class SimpleForkWrapper {
|
|||
this.worker = fork(actualWorkerPath, [], {
|
||||
execPath: this.nodeBinaryPath,
|
||||
env: workerEnv,
|
||||
silent: false,
|
||||
silent: true,
|
||||
cwd: app.isPackaged ? process.resourcesPath : process.cwd(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
updateAppSettings,
|
||||
} from "../db/app-settings";
|
||||
import type { AppSettingsData } from "../db/schema";
|
||||
import { isWindows, isMacOS } from "../utils/platform";
|
||||
import { isMacOS } from "../utils/platform";
|
||||
|
||||
/**
|
||||
* Database-backed settings service with typed configuration
|
||||
|
|
@ -61,7 +61,11 @@ export class SettingsService {
|
|||
* Get UI settings
|
||||
*/
|
||||
async getUISettings(): Promise<AppSettingsData["ui"]> {
|
||||
return await getSettingsSection("ui");
|
||||
return (
|
||||
(await getSettingsSection("ui")) ?? {
|
||||
theme: "system",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ const AppPreferencesSchema = z.object({
|
|||
showWidgetWhileInactive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const UIThemeSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"]),
|
||||
});
|
||||
|
||||
export const settingsRouter = createRouter({
|
||||
// Get all settings
|
||||
getSettings: procedure.query(async ({ ctx }) => {
|
||||
|
|
@ -549,6 +553,38 @@ export const settingsRouter = createRouter({
|
|||
return true;
|
||||
}),
|
||||
|
||||
// Update UI theme
|
||||
updateUITheme: procedure
|
||||
.input(UIThemeSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const settingsService = ctx.serviceManager.getService("settingsService");
|
||||
if (!settingsService) {
|
||||
throw new Error("SettingsService not available");
|
||||
}
|
||||
|
||||
// Get current UI settings
|
||||
const currentUISettings = await settingsService.getUISettings();
|
||||
|
||||
// Update with new theme
|
||||
await settingsService.setUISettings({
|
||||
...currentUISettings,
|
||||
theme: input.theme,
|
||||
});
|
||||
|
||||
// Update all window themes immediately
|
||||
const windowManager = ctx.serviceManager.getService("windowManager");
|
||||
if (windowManager) {
|
||||
await windowManager.updateAllWindowThemes();
|
||||
}
|
||||
|
||||
const logger = ctx.serviceManager.getLogger();
|
||||
if (logger) {
|
||||
logger.main.info("UI theme updated", { theme: input.theme });
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
// Reset app - deletes all data and restarts
|
||||
resetApp: procedure.mutation(async ({ ctx }) => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue