diff --git a/apps/frontend/electron.main.js b/apps/frontend/electron.main.js index 5494a291..6cd35294 100644 --- a/apps/frontend/electron.main.js +++ b/apps/frontend/electron.main.js @@ -2,6 +2,7 @@ const { app, BrowserWindow, ipcMain, safeStorage, shell, powerMonitor } = require("electron") const path = require("path") const { fork } = require("child_process") +const { createApplicationMenu } = require("./menu") let serverProcess const metricsProcesses = new Map() @@ -153,6 +154,7 @@ function createWindow() { } app.whenReady().then(() => { + createApplicationMenu() startServer() if (serverProcess) { serverProcess.on("message", (message) => { diff --git a/apps/frontend/menu.js b/apps/frontend/menu.js new file mode 100644 index 00000000..14f0f696 --- /dev/null +++ b/apps/frontend/menu.js @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +const { Menu, BrowserWindow, shell } = require("electron") + +function createApplicationMenu() { + const isMac = process.platform === "darwin" + + const template = [ + ...(isMac ? [{ role: "appMenu" }] : []), + { role: "fileMenu" }, + { role: "editMenu" }, + { role: "viewMenu" }, + { + label: "Shortcuts", + submenu: [ + { label: "Connections", accelerator: isMac ? "Cmd+1" : "Ctrl+1", click: () => sendNavigationEvent("connect") }, + { label: "Dashboard", accelerator: isMac ? "Cmd+2" : "Ctrl+2", click: () => sendNavigationEvent("dashboard") }, + { label: "Key Browser", accelerator: isMac ? "Cmd+3" : "Ctrl+3", click: () => sendNavigationEvent("browse") }, + { label: "Monitoring", accelerator: isMac ? "Cmd+4" : "Ctrl+4", click: () => sendNavigationEvent("monitoring") }, + { label: "Send Command", accelerator: isMac ? "Cmd+5" : "Ctrl+5", click: () => sendNavigationEvent("sendcommand") }, + { label: "Cluster Topology", accelerator: isMac ? "Cmd+6" : "Ctrl+6", click: () => sendNavigationEvent("cluster-topology") }, + { label: "Settings", accelerator: isMac ? "Cmd+7" : "Ctrl+7", click: () => sendNavigationEvent("settings") }, + { label: "Learn More", accelerator: isMac ? "Cmd+8" : "Ctrl+8", click: () => sendNavigationEvent("learnmore") }, + + ], + }, + { role: "windowMenu" }, + { + role: "help", + submenu: [ + { label: "GitHub Repository", click: async () => { await shell.openExternal("https://github.com/valkey-io/valkey-admin") } }, + ], + }, + ] + + const menu = Menu.buildFromTemplate(template) + Menu.setApplicationMenu(menu) +} + +function sendNavigationEvent(route) { + const win = BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0] + if (win) { + win.webContents.send("navigate", route) + } +} + +module.exports = { createApplicationMenu } diff --git a/apps/frontend/preload.js b/apps/frontend/preload.js index 195763b4..c081778d 100644 --- a/apps/frontend/preload.js +++ b/apps/frontend/preload.js @@ -5,3 +5,9 @@ contextBridge.exposeInMainWorld("secureStorage", { encrypt: (password) => ipcRenderer.invoke("secure-storage:encrypt", password), decrypt: (encrypted) => ipcRenderer.invoke("secure-storage:decrypt", encrypted), }) + +contextBridge.exposeInMainWorld("electronNavigation", { + onNavigate: (callback) => { + ipcRenderer.on("navigate", (_event, route) => callback(route)) + }, +}) diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index d5bc64e2..949e7c4a 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { Toaster } from "./components/ui/sonner" import { DarkModeProvider } from "./contexts/DarkModeContext" import { useWebSocketNavigation } from "./hooks/useWebSocketNavigation" import { useValkeyConnectionNavigation } from "./hooks/useValkeyConnectionNavigation" +import { useShortcutNavigation } from "./hooks/useShortcutNavigation" import { connectPending } from "@/state/wsconnection/wsConnectionSlice" function App() { @@ -14,6 +15,7 @@ function App() { useWebSocketNavigation() useValkeyConnectionNavigation() + useShortcutNavigation() useEffect(() => { dispatch(connectPending()) diff --git a/apps/frontend/src/hooks/useShortcutNavigation.ts b/apps/frontend/src/hooks/useShortcutNavigation.ts new file mode 100644 index 00000000..2abe17bf --- /dev/null +++ b/apps/frontend/src/hooks/useShortcutNavigation.ts @@ -0,0 +1,40 @@ +import { useEffect } from "react" +import { useNavigate, useParams } from "react-router" +import useIsConnected from "./useIsConnected" + +export const useShortcutNavigation = () => { + const navigate = useNavigate() + const { id, clusterId } = useParams() + const isConnected = useIsConnected() + + useEffect(() => { + // only works when running in Electron + if (!window.electronNavigation) return + + const handleNavigate = (route: string) => { + const routes: Record = isConnected + ? { + connect: clusterId ? `/${clusterId}/${id}/connect` : `/${id}/connect`, + dashboard: clusterId ? `/${clusterId}/${id}/dashboard` : `/${id}/dashboard`, + browse: clusterId ? `/${clusterId}/${id}/browse` : `/${id}/browse`, + monitoring: clusterId ? `/${clusterId}/${id}/monitoring` : `/${id}/monitoring`, + sendcommand: clusterId ? `/${clusterId}/${id}/sendcommand` : `/${id}/sendcommand`, + "cluster-topology": clusterId ? `/${clusterId}/${id}/cluster-topology` : "", + settings: clusterId ? `/${clusterId}/${id}/settings` : `/${id}/settings`, + learnmore: clusterId ? `/${clusterId}/${id}/learnmore` : `/${id}/learnmore`, + } + : { + connect: "/connect", + settings: "/settings", + learnmore: "/learnmore", + } + + const targetRoute = routes[route] + if (targetRoute) { + navigate(targetRoute) + } + } + + window.electronNavigation.onNavigate(handleNavigate) + }, [navigate, id, clusterId, isConnected]) +} diff --git a/apps/frontend/src/types/electron.d.ts b/apps/frontend/src/types/electron.d.ts new file mode 100644 index 00000000..dbbac7a1 --- /dev/null +++ b/apps/frontend/src/types/electron.d.ts @@ -0,0 +1,9 @@ +export interface ElectronNavigation { + onNavigate: (callback: (route: string) => void) => void +} + +declare global { + interface Window { + electronNavigation: ElectronNavigation + } +} diff --git a/package.json b/package.json index 898d4603..e0fc072d 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "files": [ "apps/frontend/electron.main.js", "apps/frontend/preload.js", + "apps/frontend/menu.js", "apps/frontend/dist/**/*", "package.json" ],