From c67e331ca819b0fe1564fc94b945f14ad212640e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:52:53 +0200 Subject: [PATCH 1/2] Implement Clone Window feature for File menu Co-authored-by: Byron <63622+Byron@users.noreply.github.com> --- apps/desktop/src/components/FileMenuAction.svelte | 7 +++++++ apps/desktop/src/lib/project/projectsService.ts | 9 +++++++++ crates/gitbutler-tauri/src/main.rs | 1 + crates/gitbutler-tauri/src/menu.rs | 9 +++++++++ crates/gitbutler-tauri/src/projects.rs | 10 ++++++++++ crates/gitbutler-tauri/src/window.rs | 6 ++++++ 6 files changed, 42 insertions(+) diff --git a/apps/desktop/src/components/FileMenuAction.svelte b/apps/desktop/src/components/FileMenuAction.svelte index 5d0f5fe70f..2c7cd42655 100644 --- a/apps/desktop/src/components/FileMenuAction.svelte +++ b/apps/desktop/src/components/FileMenuAction.svelte @@ -22,6 +22,13 @@ }), shortcutService.on('clone-repo', async () => { goto(clonePath()); + }), + shortcutService.on('clone-window', async () => { + const currentProjectId = await projectsService.getCurrentProjectId(); + if (currentProjectId) { + await projectsService.openProjectInNewWindow(currentProjectId); + } + // If no project is available, the menu item will be ignored (as per requirements) }) ) ); diff --git a/apps/desktop/src/lib/project/projectsService.ts b/apps/desktop/src/lib/project/projectsService.ts index ab11e514aa..a70404ceb5 100644 --- a/apps/desktop/src/lib/project/projectsService.ts +++ b/apps/desktop/src/lib/project/projectsService.ts @@ -73,6 +73,11 @@ export class ProjectsService { await this.api.endpoints.openProjectInWindow.mutate({ id: projectId }); } + async getCurrentProjectId(): Promise { + const result = await this.api.endpoints.getCurrentProjectId.query(); + return result || undefined; + } + async relocateProject(projectId: string): Promise { const path = await this.getValidPath(); if (!path) return; @@ -177,6 +182,10 @@ function injectEndpoints(api: ClientState['backendApi']) { openProjectInWindow: build.mutation({ extraOptions: { command: 'open_project_in_window' }, query: (args) => args + }), + getCurrentProjectId: build.query({ + extraOptions: { command: 'get_current_project_id' }, + query: () => undefined }) }) }); diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 2a97b1dad5..01896dc003 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -230,6 +230,7 @@ fn main() { projects::list_projects, projects::set_project_active, projects::open_project_in_window, + projects::get_current_project_id, repo::git_get_local_config, repo::git_set_local_config, repo::check_signing_settings, diff --git a/crates/gitbutler-tauri/src/menu.rs b/crates/gitbutler-tauri/src/menu.rs index 6bab3e2062..2a05e93b6f 100644 --- a/crates/gitbutler-tauri/src/menu.rs +++ b/crates/gitbutler-tauri/src/menu.rs @@ -79,6 +79,10 @@ pub fn build( .accelerator("CmdOrCtrl+Shift+O") .build(handle)?, &PredefinedMenuItem::separator(handle)?, + &MenuItemBuilder::with_id("file/clone-window", "Clone Window") + .accelerator("CmdOrCtrl+Shift+N") + .build(handle)?, + &PredefinedMenuItem::separator(handle)?, ]) .build()?; @@ -265,6 +269,11 @@ pub fn handle_event( return; } + if event.id() == "file/clone-window" { + emit(webview, SHORTCUT_EVENT, "clone-window"); + return; + } + #[cfg(any(debug_assertions, feature = "devtools"))] { if event.id() == "view/devtools" { diff --git a/crates/gitbutler-tauri/src/projects.rs b/crates/gitbutler-tauri/src/projects.rs index ec21421f17..db4f5a53a3 100644 --- a/crates/gitbutler-tauri/src/projects.rs +++ b/crates/gitbutler-tauri/src/projects.rs @@ -114,6 +114,16 @@ pub fn open_project_in_window(handle: tauri::AppHandle, id: ProjectId) -> Result Ok(()) } +/// Get the current project ID for a window, if any. +#[tauri::command] +#[instrument(skip(window_state, window), err(Debug))] +pub fn get_current_project_id( + window_state: State<'_, WindowState>, + window: Window, +) -> Result, Error> { + Ok(window_state.get_project_for_window(window.label())) +} + #[derive(serde::Deserialize, serde::Serialize)] pub struct ProjectForFrontend { #[serde(flatten)] diff --git a/crates/gitbutler-tauri/src/window.rs b/crates/gitbutler-tauri/src/window.rs index 510ec594f1..0ac0e4067b 100644 --- a/crates/gitbutler-tauri/src/window.rs +++ b/crates/gitbutler-tauri/src/window.rs @@ -276,6 +276,12 @@ pub(crate) mod state { .map(|state| state.project_id) .collect() } + + /// Get the current project ID for a specific window, if any. + pub fn get_project_for_window(&self, window: &WindowLabelRef) -> Option { + let state_by_label = self.state.lock(); + state_by_label.get(window).map(|state| state.project_id) + } } } From 88e7da679f4ba63dd66cf9518ecf393711bc8ec6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 12 Sep 2025 08:52:36 +0200 Subject: [PATCH 2/2] make project id handling work --- apps/desktop/src/components/FileMenuAction.svelte | 4 ++-- apps/desktop/src/lib/project/projectsService.ts | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/FileMenuAction.svelte b/apps/desktop/src/components/FileMenuAction.svelte index 2c7cd42655..f58b9c4208 100644 --- a/apps/desktop/src/components/FileMenuAction.svelte +++ b/apps/desktop/src/components/FileMenuAction.svelte @@ -24,11 +24,11 @@ goto(clonePath()); }), shortcutService.on('clone-window', async () => { - const currentProjectId = await projectsService.getCurrentProjectId(); + const url = new URL(window.location.href); + const currentProjectId = url.pathname.split('/')[1]; if (currentProjectId) { await projectsService.openProjectInNewWindow(currentProjectId); } - // If no project is available, the menu item will be ignored (as per requirements) }) ) ); diff --git a/apps/desktop/src/lib/project/projectsService.ts b/apps/desktop/src/lib/project/projectsService.ts index a70404ceb5..d060149912 100644 --- a/apps/desktop/src/lib/project/projectsService.ts +++ b/apps/desktop/src/lib/project/projectsService.ts @@ -73,11 +73,6 @@ export class ProjectsService { await this.api.endpoints.openProjectInWindow.mutate({ id: projectId }); } - async getCurrentProjectId(): Promise { - const result = await this.api.endpoints.getCurrentProjectId.query(); - return result || undefined; - } - async relocateProject(projectId: string): Promise { const path = await this.getValidPath(); if (!path) return;