Merge pull request #40 from alexispurslane/tauri-native-menu
Implement complete native menu system with keyboard accelerators
This commit is contained in:
@@ -3,15 +3,14 @@
|
||||
"identifier": "main-window-native-dialogs",
|
||||
"description": "Grant the main window access to required core features and native dialog commands.",
|
||||
"remote": {
|
||||
"urls": [
|
||||
"http://127.0.0.1:*",
|
||||
"http://localhost:*"
|
||||
]
|
||||
"urls": ["http://127.0.0.1:*", "http://localhost:*"]
|
||||
},
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:menu:default",
|
||||
"dialog:allow-open",
|
||||
"opener:allow-default-urls"
|
||||
"opener:allow-default-urls",
|
||||
"core:webview:allow-set-webview-zoom"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"main-window-native-dialogs":{"identifier":"main-window-native-dialogs","description":"Grant the main window access to required core features and native dialog commands.","remote":{"urls":["http://127.0.0.1:*","http://localhost:*"]},"local":true,"windows":["main"],"permissions":["core:default","dialog:allow-open","opener:allow-default-urls"]}}
|
||||
{"main-window-native-dialogs":{"identifier":"main-window-native-dialogs","description":"Grant the main window access to required core features and native dialog commands.","remote":{"urls":["http://127.0.0.1:*","http://localhost:*"]},"local":true,"windows":["main"],"permissions":["core:default","core:menu:default","dialog:allow-open","opener:allow-default-urls","core:webview:allow-set-webview-zoom"]}}
|
||||
@@ -4,7 +4,7 @@ mod cli_manager;
|
||||
|
||||
use cli_manager::{CliProcessManager, CliStatus};
|
||||
use serde_json::json;
|
||||
use tauri::menu::Menu;
|
||||
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
||||
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
||||
use tauri::webview::Webview;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Wry};
|
||||
@@ -84,8 +84,81 @@ fn main() {
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![cli_get_status, cli_restart])
|
||||
.on_menu_event(|_app_handle, _event| {
|
||||
// No menu items defined currently
|
||||
.on_menu_event(|app_handle, event| {
|
||||
match event.id().0.as_str() {
|
||||
// File menu
|
||||
"new_instance" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.emit("menu:newInstance", ());
|
||||
}
|
||||
}
|
||||
"close" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.close();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app_handle.exit(0);
|
||||
}
|
||||
|
||||
// View menu
|
||||
"reload" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.eval("window.location.reload()");
|
||||
}
|
||||
}
|
||||
"force_reload" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.eval("window.location.reload(true)");
|
||||
}
|
||||
}
|
||||
"toggle_devtools" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
window.open_devtools();
|
||||
}
|
||||
}
|
||||
|
||||
"toggle_fullscreen" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false));
|
||||
}
|
||||
}
|
||||
|
||||
// Window menu
|
||||
"minimize" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.minimize();
|
||||
}
|
||||
}
|
||||
"zoom" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.maximize();
|
||||
}
|
||||
}
|
||||
|
||||
// App menu (macOS)
|
||||
"about" => {
|
||||
// TODO: Implement about dialog
|
||||
println!("About menu item clicked");
|
||||
}
|
||||
"hide" => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
"hide_others" => {
|
||||
// TODO: Hide other app windows
|
||||
println!("Hide Others menu item clicked");
|
||||
}
|
||||
"show_all" => {
|
||||
// TODO: Show all app windows
|
||||
println!("Show All menu item clicked");
|
||||
}
|
||||
|
||||
_ => {
|
||||
println!("Unhandled menu event: {}", event.id().0);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
@@ -118,8 +191,77 @@ fn main() {
|
||||
}
|
||||
|
||||
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||
// Minimal empty menu for now (Tauri v2 menu API differs from v1 roles).
|
||||
let menu = Menu::new(app)?;
|
||||
let is_mac = cfg!(target_os = "macos");
|
||||
|
||||
// Create submenus
|
||||
let mut submenus = Vec::new();
|
||||
|
||||
// App menu (macOS only)
|
||||
if is_mac {
|
||||
let app_menu = SubmenuBuilder::new(app, "CodeNomad")
|
||||
.text("about", "About CodeNomad")
|
||||
.separator()
|
||||
.text("hide", "Hide CodeNomad")
|
||||
.text("hide_others", "Hide Others")
|
||||
.text("show_all", "Show All")
|
||||
.separator()
|
||||
.text("quit", "Quit CodeNomad")
|
||||
.build()?;
|
||||
submenus.push(app_menu);
|
||||
}
|
||||
|
||||
// File menu - create New Instance with accelerator
|
||||
let new_instance_item = MenuItem::with_id(
|
||||
app,
|
||||
"new_instance",
|
||||
"New Instance",
|
||||
true,
|
||||
Some("CmdOrCtrl+N")
|
||||
)?;
|
||||
|
||||
let file_menu = SubmenuBuilder::new(app, "File")
|
||||
.item(&new_instance_item)
|
||||
.separator()
|
||||
.text(if is_mac { "close" } else { "quit" }, if is_mac { "Close" } else { "Quit" })
|
||||
.build()?;
|
||||
submenus.push(file_menu);
|
||||
|
||||
// Edit menu with predefined items for standard functionality
|
||||
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
||||
.undo()
|
||||
.redo()
|
||||
.separator()
|
||||
.cut()
|
||||
.copy()
|
||||
.paste()
|
||||
.separator()
|
||||
.select_all()
|
||||
.build()?;
|
||||
submenus.push(edit_menu);
|
||||
|
||||
// View menu
|
||||
let view_menu = SubmenuBuilder::new(app, "View")
|
||||
.text("reload", "Reload")
|
||||
.text("force_reload", "Force Reload")
|
||||
.text("toggle_devtools", "Toggle Developer Tools")
|
||||
.separator()
|
||||
|
||||
.separator()
|
||||
.text("toggle_fullscreen", "Toggle Full Screen")
|
||||
.build()?;
|
||||
submenus.push(view_menu);
|
||||
|
||||
// Window menu
|
||||
let window_menu = SubmenuBuilder::new(app, "Window")
|
||||
.text("minimize", "Minimize")
|
||||
.text("zoom", "Zoom")
|
||||
.build()?;
|
||||
submenus.push(window_menu);
|
||||
|
||||
// Build the main menu with all submenus
|
||||
let submenu_refs: Vec<&dyn tauri::menu::IsMenuItem<_>> = submenus.iter().map(|s| s as &dyn tauri::menu::IsMenuItem<_>).collect();
|
||||
let menu = MenuBuilder::new(app).items(&submenu_refs).build()?;
|
||||
|
||||
app.set_menu(menu)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"fullscreen": false,
|
||||
"decorations": true,
|
||||
"theme": "Dark",
|
||||
"backgroundColor": "#1a1a1a"
|
||||
"backgroundColor": "#1a1a1a",
|
||||
"zoomHotkeysEnabled": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Show, createMemo, createEffect, createSignal } from "solid-js"
|
||||
import { Component, Show, createMemo, createEffect, createSignal, onMount, onCleanup } from "solid-js"
|
||||
import { Dialog } from "@kobalte/core/dialog"
|
||||
import { Toaster } from "solid-toast"
|
||||
import AlertDialog from "./components/alert-dialog"
|
||||
@@ -14,6 +14,7 @@ import { useCommands } from "./lib/hooks/use-commands"
|
||||
import { useAppLifecycle } from "./lib/hooks/use-app-lifecycle"
|
||||
import { getLogger } from "./lib/logger"
|
||||
import { initReleaseNotifications } from "./stores/releases"
|
||||
import { runtimeEnv } from "./lib/runtime-env"
|
||||
import {
|
||||
hasInstances,
|
||||
isSelectingFolder,
|
||||
@@ -249,6 +250,28 @@ const App: Component = () => {
|
||||
getActiveSessionIdForInstance: activeSessionIdForInstance,
|
||||
})
|
||||
|
||||
// Listen for Tauri menu events
|
||||
onMount(() => {
|
||||
if (runtimeEnv.host === "tauri") {
|
||||
const tauriBridge = (window as { __TAURI__?: { event?: { listen: (event: string, handler: (event: { payload: unknown }) => void) => Promise<() => void> } } }).__TAURI__
|
||||
if (tauriBridge?.event) {
|
||||
let unlistenMenu: (() => void) | null = null
|
||||
|
||||
tauriBridge.event.listen("menu:newInstance", () => {
|
||||
handleNewInstanceRequest()
|
||||
}).then((unlisten) => {
|
||||
unlistenMenu = unlisten
|
||||
}).catch((error) => {
|
||||
log.error("Failed to listen for menu:newInstance event", error)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
unlistenMenu?.()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<InstanceDisconnectedModal
|
||||
|
||||
Reference in New Issue
Block a user