diff --git a/apps/code/src/main/menu.ts b/apps/code/src/main/menu.ts index ca79b8dfa8..e6b8f133fa 100644 --- a/apps/code/src/main/menu.ts +++ b/apps/code/src/main/menu.ts @@ -25,14 +25,14 @@ import { saveZoomLevel } from "./utils/store"; // Zoom is measured in Electron "levels" (factor = 1.2 ** level; 0 = 100%). // ZOOM_STEP is one Zoom In/Out notch; the bounds clamp the level so a runaway // accelerator can't persist an unusable zoom across restarts. -const ZOOM_STEP = 0.5; +export const ZOOM_STEP = 0.5; const ZOOM_MIN = -3; const ZOOM_MAX = 3; // Apply a zoom change to the focused window and persist the new level so it // survives restarts. `delta` adjusts relative to the current level; "reset" // returns to 100%. -function applyZoom(delta: number | "reset"): void { +export function applyZoom(delta: number | "reset"): void { const webContents = BrowserWindow.getFocusedWindow()?.webContents; if (!webContents) return; const next = delta === "reset" ? 0 : webContents.getZoomLevel() + delta; diff --git a/apps/code/src/main/platform-adapters/electron-main-window.ts b/apps/code/src/main/platform-adapters/electron-main-window.ts index abdc6b2771..2a758f0d51 100644 --- a/apps/code/src/main/platform-adapters/electron-main-window.ts +++ b/apps/code/src/main/platform-adapters/electron-main-window.ts @@ -1,6 +1,7 @@ import type { IMainWindow } from "@posthog/platform/main-window"; import { app, type BrowserWindow } from "electron"; import { injectable } from "inversify"; +import { applyZoom, ZOOM_STEP } from "../menu"; @injectable() export class ElectronMainWindow implements IMainWindow { @@ -35,4 +36,16 @@ export class ElectronMainWindow implements IMainWindow { app.on("browser-window-focus", listener); return () => app.off("browser-window-focus", listener); } + + public zoomIn(): void { + applyZoom(ZOOM_STEP); + } + + public zoomOut(): void { + applyZoom(-ZOOM_STEP); + } + + public resetZoom(): void { + applyZoom("reset"); + } } diff --git a/packages/host-router/src/routers/os.router.ts b/packages/host-router/src/routers/os.router.ts index 77cd41f094..364e972fbd 100644 --- a/packages/host-router/src/routers/os.router.ts +++ b/packages/host-router/src/routers/os.router.ts @@ -1,4 +1,8 @@ import { publicProcedure, router } from "@posthog/host-trpc/trpc"; +import { + type IMainWindow, + MAIN_WINDOW_SERVICE, +} from "@posthog/platform/main-window"; import { OS_SERVICE } from "@posthog/workspace-server/services/os/identifiers"; import type { OsService } from "@posthog/workspace-server/services/os/os"; import { @@ -120,4 +124,16 @@ export const osRouter = router({ .get(OS_SERVICE) .saveClipboardFile(input.base64Data, input.originalName), ), + + zoomIn: publicProcedure.mutation(({ ctx }) => + ctx.container.get(MAIN_WINDOW_SERVICE).zoomIn(), + ), + + zoomOut: publicProcedure.mutation(({ ctx }) => + ctx.container.get(MAIN_WINDOW_SERVICE).zoomOut(), + ), + + resetZoom: publicProcedure.mutation(({ ctx }) => + ctx.container.get(MAIN_WINDOW_SERVICE).resetZoom(), + ), }); diff --git a/packages/platform/src/main-window.ts b/packages/platform/src/main-window.ts index e5f6f9cbfc..01ae74d9ae 100644 --- a/packages/platform/src/main-window.ts +++ b/packages/platform/src/main-window.ts @@ -4,6 +4,9 @@ export interface IMainWindow { isMinimized(): boolean; restore(): void; onFocus(handler: () => void): () => void; + zoomIn(): void; + zoomOut(): void; + resetZoom(): void; } export const MAIN_WINDOW_SERVICE = Symbol.for("posthog.platform.mainWindow"); diff --git a/packages/shared/src/analytics-events.ts b/packages/shared/src/analytics-events.ts index 367176ca7e..5219e4efe7 100644 --- a/packages/shared/src/analytics-events.ts +++ b/packages/shared/src/analytics-events.ts @@ -54,7 +54,10 @@ export type CommandMenuAction = | "search-files" | "open-file" | "reload-window" - | "show-log-folder"; + | "show-log-folder" + | "zoom-in" + | "zoom-out" + | "zoom-reset"; // Event property interfaces export interface TaskListViewProperties { diff --git a/packages/ui/src/features/command/CommandMenu.tsx b/packages/ui/src/features/command/CommandMenu.tsx index 945518534a..446942dd8f 100644 --- a/packages/ui/src/features/command/CommandMenu.tsx +++ b/packages/ui/src/features/command/CommandMenu.tsx @@ -1,4 +1,9 @@ import { CaretLeftIcon, CaretRightIcon, HashIcon } from "@phosphor-icons/react"; +import { resolveService } from "@posthog/di/container"; +import { + HOST_TRPC_CLIENT, + type HostTrpcClient, +} from "@posthog/host-router/client"; import { Autocomplete, AutocompleteCollection, @@ -58,6 +63,8 @@ import { ReloadIcon, SunIcon, ViewVerticalIcon, + ZoomInIcon, + ZoomOutIcon, } from "@radix-ui/react-icons"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -319,9 +326,49 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) { }, ]; + const view: Command[] = [ + { + id: "zoom-in", + label: "Zoom in", + keywords: "zoom increase larger", + icon: , + action: "zoom-in", + shortcut: SHORTCUTS.ZOOM_IN, + onRun: () => + void resolveService( + HOST_TRPC_CLIENT, + ).os.zoomIn.mutate(), + }, + { + id: "zoom-out", + label: "Zoom out", + keywords: "zoom decrease smaller", + icon: , + action: "zoom-out", + shortcut: SHORTCUTS.ZOOM_OUT, + onRun: () => + void resolveService( + HOST_TRPC_CLIENT, + ).os.zoomOut.mutate(), + }, + { + id: "zoom-reset", + label: "Reset zoom", + keywords: "zoom actual size default", + icon: , + action: "zoom-reset", + shortcut: SHORTCUTS.RESET_ZOOM, + onRun: () => + void resolveService( + HOST_TRPC_CLIENT, + ).os.resetZoom.mutate(), + }, + ]; + const out: CommandSection[] = [ { label: "Actions", items: actions }, { label: "Navigation", items: navigation }, + { label: "View", items: view }, { label: "Developer", items: developer }, ]; diff --git a/packages/ui/src/features/command/keyboard-shortcuts.ts b/packages/ui/src/features/command/keyboard-shortcuts.ts index 87ecb99e35..1a9e2e55fa 100644 --- a/packages/ui/src/features/command/keyboard-shortcuts.ts +++ b/packages/ui/src/features/command/keyboard-shortcuts.ts @@ -26,6 +26,9 @@ export const SHORTCUTS = { SUBMIT_BLUR: "mod+enter", SWITCH_MESSAGING_MODE: "mod+s", RELOAD_WINDOW: "mod+shift+r", + ZOOM_IN: "mod+=", + ZOOM_OUT: "mod+-", + RESET_ZOOM: "mod+0", } as const; export type ShortcutCategory = "general" | "navigation" | "panels" | "editor"; @@ -256,6 +259,8 @@ function formatKey(key: string): string { if (k === ",") return ","; if (k === "[") return "["; if (k === "]") return "]"; + if (k === "=") return "+"; + if (k === "-") return "-"; if (k === "tab") return "Tab"; return k.toUpperCase(); }