a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add native File menu with Open Project dialog

+120 -23
+120 -23
apps/desktop/src-tauri/src/lib.rs
··· 10 10 use std::path::{Path, PathBuf}; 11 11 use std::sync::Arc; 12 12 use storage::{AppLayout, Project, Storage, get_storage}; 13 - use tauri::Manager; 14 - use tauri::menu::{MenuBuilder, SubmenuBuilder}; 13 + use tauri::{AppHandle, Emitter, Manager}; 14 + use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder}; 15 + use tauri_plugin_dialog::DialogExt; 15 16 use watcher::{WatcherManager, get_watcher_manager}; 16 17 17 18 #[derive(Serialize)] ··· 337 338 repo::log::resolve_revset(path, &revset).map_err(|e| format!("Failed to resolve revset: {}", e)) 338 339 } 339 340 341 + /// Handle "Open Project" menu action: show folder picker, find jj repo, save project, emit event 342 + fn handle_open_project(app_handle: &AppHandle) { 343 + let handle = app_handle.clone(); 344 + 345 + app_handle.dialog().file().pick_folder(move |folder_path| { 346 + let Some(folder) = folder_path else { return }; 347 + let path_str = folder.to_string(); 348 + 349 + // Find jj repo root 350 + let Some(repo_path) = repo::find_jj_repo(&PathBuf::from(&path_str)) else { 351 + // TODO: Could show an error dialog here 352 + return; 353 + }; 354 + let repo_path_str = repo_path.to_string_lossy().to_string(); 355 + 356 + // Save project and emit event for frontend navigation 357 + let handle_clone = handle.clone(); 358 + tauri::async_runtime::spawn(async move { 359 + let storage = get_storage(&handle_clone); 360 + 361 + // Check if project already exists 362 + let existing = storage.find_project_by_path(&repo_path_str).await.ok().flatten(); 363 + let project_id = existing.as_ref().map(|p| p.id.clone()) 364 + .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); 365 + 366 + let name = repo_path.file_name() 367 + .and_then(|n| n.to_str()) 368 + .unwrap_or("Unknown") 369 + .to_string(); 370 + 371 + let project = Project { 372 + id: project_id.clone(), 373 + path: repo_path_str, 374 + name, 375 + last_opened_at: chrono::Utc::now().timestamp_millis(), 376 + revset_preset: None, 377 + }; 378 + 379 + if let Err(e) = storage.upsert_project(&project).await { 380 + eprintln!("Failed to save project: {}", e); 381 + return; 382 + } 383 + 384 + // Emit event for frontend to navigate 385 + let _ = handle_clone.emit("open-project", project_id); 386 + }); 387 + }); 388 + } 389 + 390 + fn build_app_menu(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> { 391 + let open_project = MenuItem::with_id(app, "open-project", "Open Project...", true, Some("Ctrl+Cmd+O"))?; 392 + 393 + let file_menu = SubmenuBuilder::new(app, "File") 394 + .item(&open_project) 395 + .separator() 396 + .close_window() 397 + .build()?; 398 + 399 + let edit_menu = SubmenuBuilder::new(app, "Edit") 400 + .undo() 401 + .redo() 402 + .separator() 403 + .cut() 404 + .copy() 405 + .paste() 406 + .select_all() 407 + .build()?; 408 + 409 + let view_menu = SubmenuBuilder::new(app, "View") 410 + .fullscreen() 411 + .build()?; 412 + 413 + let window_menu = SubmenuBuilder::new(app, "Window") 414 + .minimize() 415 + .maximize() 416 + .separator() 417 + .close_window() 418 + .build()?; 419 + 420 + #[cfg(debug_assertions)] 421 + let reload_item = MenuItem::with_id(app, "reload", "Reload", true, Some("CmdOrCtrl+R"))?; 422 + 423 + #[cfg(debug_assertions)] 424 + let debug_menu = SubmenuBuilder::new(app, "Debug") 425 + .item(&reload_item) 426 + .build()?; 427 + 428 + let mut menu_builder = MenuBuilder::new(app) 429 + .item(&file_menu) 430 + .item(&edit_menu) 431 + .item(&view_menu) 432 + .item(&window_menu); 433 + 434 + #[cfg(debug_assertions)] 435 + { 436 + menu_builder = menu_builder.item(&debug_menu); 437 + } 438 + 439 + let menu = menu_builder.build()?; 440 + app.set_menu(menu)?; 441 + 442 + Ok(()) 443 + } 444 + 340 445 #[cfg_attr(mobile, tauri::mobile_entry_point)] 341 446 pub fn run() { 342 447 tauri::Builder::default() ··· 359 464 360 465 app.handle().manage(WatcherManager::new()); 361 466 362 - // Add Debug menu for development 363 - #[cfg(debug_assertions)] 364 - { 365 - let debug_menu = SubmenuBuilder::new(app, "Debug") 366 - .text("reload", "Reload") 367 - .build()?; 368 - 369 - let menu = MenuBuilder::new(app) 370 - .copy() 371 - .paste() 372 - .undo() 373 - .redo() 374 - .separator() 375 - .item(&debug_menu) 376 - .build()?; 377 - 378 - app.set_menu(menu)?; 467 + // Build application menu 468 + if let Err(e) = build_app_menu(app) { 469 + eprintln!("Failed to build menu: {}", e); 470 + } 379 471 380 - app.on_menu_event(|app_handle, event| { 381 - if event.id().0.as_str() == "reload" { 472 + // Handle menu events 473 + app.on_menu_event(|app_handle, event| { 474 + match event.id().0.as_str() { 475 + "open-project" => handle_open_project(app_handle), 476 + #[cfg(debug_assertions)] 477 + "reload" => { 382 478 if let Some(window) = app_handle.get_webview_window("main") { 383 479 let _ = window.eval("window.location.reload()"); 384 480 } 385 481 } 386 - }); 387 - } 482 + _ => {} 483 + } 484 + }); 388 485 389 486 Ok(()) 390 487 })