Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/staged/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1930,6 +1930,13 @@ pub fn run() {
true,
Some("CmdOrCtrl+Shift+G"),
)?;
let delete_project_item = MenuItem::with_id(
handle,
"delete_project",
"Delete Project",
true,
Some("CmdOrCtrl+Backspace"),
)?;
let zoom_in_item =
MenuItem::with_id(handle, "zoom_in", "Zoom In", true, Some("CmdOrCtrl+="))?;
let zoom_out_item =
Expand Down Expand Up @@ -1982,6 +1989,7 @@ pub fn run() {
&PredefinedMenuItem::cut(handle, None)?,
&PredefinedMenuItem::copy(handle, None)?,
&PredefinedMenuItem::paste(handle, None)?,
&delete_project_item,
&PredefinedMenuItem::select_all(handle, None)?,
&PredefinedMenuItem::separator(handle)?,
&find_item,
Expand Down Expand Up @@ -2169,6 +2177,7 @@ pub fn run() {
"find" => Some("menu:find"),
"find_next" => Some("menu:find-next"),
"find_previous" => Some("menu:find-previous"),
"delete_project" => Some("menu:delete-project"),
"zoom_in" => Some("menu:zoom-in"),
"zoom_out" => Some("menu:zoom-out"),
"zoom_reset" => Some("menu:zoom-reset"),
Expand Down
31 changes: 31 additions & 0 deletions apps/staged/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
let unlistenFind: UnlistenFn | undefined;
let unlistenFindNext: UnlistenFn | undefined;
let unlistenFindPrevious: UnlistenFn | undefined;
let unlistenDeleteProject: UnlistenFn | undefined;
let unlistenZoomIn: UnlistenFn | undefined;
let unlistenZoomOut: UnlistenFn | undefined;
let unlistenZoomReset: UnlistenFn | undefined;
Expand Down Expand Up @@ -138,6 +139,24 @@
return true;
}

function isTextInputActive(): boolean {
const target = document.activeElement;
if (!(target instanceof HTMLElement)) return false;
if (target.isContentEditable) return true;
return (
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT'
);
}

function requestDeleteCurrentProject(): boolean {
if (isTextInputActive()) return false;
if (navigation.currentRoute.kind !== 'project') return false;

const event = new CustomEvent('staged:delete-current-project', { cancelable: true });
window.dispatchEvent(event);
return event.defaultPrevented;
}

async function logUpdater(message: string) {
console.warn(message);
try {
Expand Down Expand Up @@ -261,6 +280,9 @@
unlistenFindPrevious = listenToEvent('menu:find-previous', () => {
if (!triggerShortcut('search-find-previous')) runSearchShortcut('previous');
});
unlistenDeleteProject = listenToEvent('menu:delete-project', () => {
triggerShortcut('app-delete-project');
});
unlistenZoomIn = listenToEvent('menu:zoom-in', () => {
if (!triggerShortcut('view-increase-size')) increaseSize();
});
Expand Down Expand Up @@ -340,6 +362,14 @@
modifiers: { meta: true },
handler: navigateBack,
},
{
id: 'app-delete-project',
description: 'Remove current project',
category: 'app',
keys: ['Backspace', 'Delete'],
modifiers: { meta: true },
handler: requestDeleteCurrentProject,
},
{
id: 'search-find',
description: 'Find in open note/session',
Expand Down Expand Up @@ -449,6 +479,7 @@
unlistenFind?.();
unlistenFindNext?.();
unlistenFindPrevious?.();
unlistenDeleteProject?.();
unlistenZoomIn?.();
unlistenZoomOut?.();
unlistenZoomReset?.();
Expand Down
26 changes: 26 additions & 0 deletions apps/staged/src/lib/features/projects/ProjectHome.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@
let branchToDelete = $state<{ branch: Branch; project: Project } | null>(null);
let deletingBranches = $state<Set<string>>(new Set());
let deletingProjectNames = $state<Map<string, string>>(new Map());
// Guards the delete shortcut while the async safe-to-delete check is in flight,
// before projectToDelete/deletingProjectNames are set, so a held key only deletes once.
let deleteShortcutPending = $state(false);

// Setup errors come from the shared workspace lifecycle orchestrator.
let worktreeErrors = $derived(workspaceLifecycle.getWorktreeErrors());
Expand All @@ -138,6 +141,8 @@
const onCacheStale = () => loadData();
window.addEventListener('staged:new-project', onNewProject);
window.addEventListener('cache-stale', onCacheStale);
const onDeleteCurrentProject = (event: Event) => handleDeleteCurrentProjectShortcut(event);
window.addEventListener('staged:delete-current-project', onDeleteCurrentProject);

const unlistenDetection = listenToRepoActionsDetection((event) => {
const matchingProjectIds = projects
Expand Down Expand Up @@ -273,6 +278,7 @@
loadGeneration++;
window.removeEventListener('staged:new-project', onNewProject);
window.removeEventListener('cache-stale', onCacheStale);
window.removeEventListener('staged:delete-current-project', onDeleteCurrentProject);
unlistenDetection();
unlistenProjectRepoAdded();
unlistenPrStatus();
Expand Down Expand Up @@ -871,6 +877,26 @@
}
}

function handleDeleteCurrentProjectShortcut(event: Event) {
if (
!selectedProject ||
deleteShortcutPending ||
selectedProjectDeleting ||
projectToDelete ||
branchToDelete ||
showNewProjectModal ||
showAddRepoModal
) {
return;
}

event.preventDefault();
deleteShortcutPending = true;
void handleDeleteProjectRequest(selectedProject).finally(() => {
deleteShortcutPending = false;
});
Comment on lines +895 to +897

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Suppress repeated delete shortcut until keyup

When the current project is safe to delete, such as a project with repoCount === 0, handleDeleteProjectRequest can finish before the user releases Cmd+Backspace. This finally then clears the only repeat guard after confirmDeleteProject() has already navigated to the next project, so subsequent auto-repeat keydown events from the same key hold will dispatch again and remove additional projects without another confirmation. Keep the shortcut latched until keyup or ignore KeyboardEvent.repeat for this destructive action.

Useful? React with 👍 / 👎.

}

async function confirmDeleteProject() {
if (!projectToDelete) return;
const id = projectToDelete.id;
Expand Down