mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
MAESTRO: Add handleCloseCurrentTab function for Cmd+W with unified tab support
Implements handleCloseCurrentTab that: - Determines which tab is active (file tab via activeFileTabId first, then AI tab via activeTabId) - Closes file tabs immediately without wizard confirmation - For AI tabs, returns info to allow keyboard handler to show wizard confirmation - Prevents closing the last AI tab (keeps at least one AI tab) Updated keyboard handler (Cmd+W) to use handleCloseCurrentTab for unified tab support.
This commit is contained in:
@@ -5577,6 +5577,112 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Close the currently active tab (for Cmd+W).
|
||||
* Determines which tab is active (checking activeFileTabId first, then activeTabId),
|
||||
* and calls the appropriate close handler.
|
||||
*
|
||||
* For file tabs: closes immediately.
|
||||
* For AI tabs: prevents closing if it's the last AI tab (keeps at least one AI tab).
|
||||
*
|
||||
* Returns an object indicating what action was taken, for the keyboard handler
|
||||
* to potentially show confirmation modals for wizard tabs.
|
||||
*/
|
||||
const handleCloseCurrentTab = useCallback((): {
|
||||
type: 'file' | 'ai' | 'prevented' | 'none';
|
||||
tabId?: string;
|
||||
isWizardTab?: boolean;
|
||||
} => {
|
||||
const session = sessionsRef.current.find((s) => s.id === activeSessionIdRef.current);
|
||||
if (!session) return { type: 'none' };
|
||||
|
||||
// Check if a file tab is active first
|
||||
if (session.activeFileTabId) {
|
||||
const tabId = session.activeFileTabId;
|
||||
// File tabs can always be closed (no wizard confirmation needed)
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
if (s.id !== activeSessionIdRef.current) return s;
|
||||
|
||||
// Find the tab to close
|
||||
const tabToClose = s.filePreviewTabs.find((tab) => tab.id === tabId);
|
||||
if (!tabToClose) return s;
|
||||
|
||||
// Remove from filePreviewTabs
|
||||
const updatedFilePreviewTabs = s.filePreviewTabs.filter(
|
||||
(tab) => tab.id !== tabId
|
||||
);
|
||||
|
||||
// Remove from unifiedTabOrder
|
||||
const closedTabIndex = s.unifiedTabOrder.findIndex(
|
||||
(ref) => ref.type === 'file' && ref.id === tabId
|
||||
);
|
||||
const updatedUnifiedTabOrder = s.unifiedTabOrder.filter(
|
||||
(ref) => !(ref.type === 'file' && ref.id === tabId)
|
||||
);
|
||||
|
||||
// Determine new active tab if we closed the active file tab
|
||||
let newActiveFileTabId: string | null = null;
|
||||
let newActiveTabId = s.activeTabId;
|
||||
|
||||
// This was the active tab - find the next tab in unifiedTabOrder
|
||||
if (updatedUnifiedTabOrder.length > 0 && closedTabIndex !== -1) {
|
||||
// Try to select the tab at the same position (or previous if at end)
|
||||
const newIndex = Math.min(
|
||||
closedTabIndex,
|
||||
updatedUnifiedTabOrder.length - 1
|
||||
);
|
||||
const nextTabRef = updatedUnifiedTabOrder[newIndex];
|
||||
|
||||
if (nextTabRef.type === 'file') {
|
||||
// Next tab is a file tab
|
||||
newActiveFileTabId = nextTabRef.id;
|
||||
} else {
|
||||
// Next tab is an AI tab - switch to it
|
||||
newActiveTabId = nextTabRef.id;
|
||||
newActiveFileTabId = null;
|
||||
}
|
||||
} else if (updatedUnifiedTabOrder.length > 0) {
|
||||
// Fallback: just select the first available tab
|
||||
const firstTabRef = updatedUnifiedTabOrder[0];
|
||||
if (firstTabRef.type === 'file') {
|
||||
newActiveFileTabId = firstTabRef.id;
|
||||
} else {
|
||||
newActiveTabId = firstTabRef.id;
|
||||
newActiveFileTabId = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...s,
|
||||
filePreviewTabs: updatedFilePreviewTabs,
|
||||
unifiedTabOrder: updatedUnifiedTabOrder,
|
||||
activeFileTabId: newActiveFileTabId,
|
||||
activeTabId: newActiveTabId,
|
||||
};
|
||||
})
|
||||
);
|
||||
return { type: 'file', tabId };
|
||||
}
|
||||
|
||||
// AI tab is active
|
||||
if (session.activeTabId) {
|
||||
// Prevent closing if it's the last AI tab
|
||||
if (session.aiTabs.length <= 1) {
|
||||
return { type: 'prevented' };
|
||||
}
|
||||
|
||||
const tabId = session.activeTabId;
|
||||
const tab = session.aiTabs.find((t) => t.id === tabId);
|
||||
const isWizardTab = tab ? hasActiveWizard(tab) : false;
|
||||
|
||||
// Return info for the keyboard handler to show confirmation modal if needed
|
||||
return { type: 'ai', tabId, isWizardTab };
|
||||
}
|
||||
|
||||
return { type: 'none' };
|
||||
}, []);
|
||||
|
||||
const handleRemoveQueuedItem = useCallback((itemId: string) => {
|
||||
setSessions((prev) =>
|
||||
prev.map((s) => {
|
||||
@@ -12242,6 +12348,9 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
handleCloseTabsLeft,
|
||||
handleCloseTabsRight,
|
||||
|
||||
// Close current tab (Cmd+W) - works with both file and AI tabs
|
||||
handleCloseCurrentTab,
|
||||
|
||||
// Session bookmark toggle
|
||||
toggleBookmark,
|
||||
};
|
||||
|
||||
@@ -462,36 +462,41 @@ export function useMainKeyboardHandler(): UseMainKeyboardHandlerReturn {
|
||||
}
|
||||
if (ctx.isTabShortcut(e, 'closeTab')) {
|
||||
e.preventDefault();
|
||||
const activeTab = ctx.activeSession.aiTabs.find(
|
||||
(t: AITab) => t.id === ctx.activeSession.activeTabId
|
||||
);
|
||||
// Use handleCloseCurrentTab to close the active tab (file or AI)
|
||||
// This handles both file preview tabs and AI tabs with unified tab system
|
||||
const closeResult = ctx.handleCloseCurrentTab();
|
||||
|
||||
// Check if this is a wizard tab - show confirmation before closing
|
||||
if (activeTab && ctx.hasActiveWizard && ctx.hasActiveWizard(activeTab)) {
|
||||
ctx.setConfirmModalMessage(
|
||||
'Close this wizard? Your progress will be lost and cannot be restored.'
|
||||
);
|
||||
ctx.setConfirmModalOnConfirm(() => () => {
|
||||
ctx.performTabClose(ctx.activeSession.activeTabId);
|
||||
trackShortcut('closeTab');
|
||||
});
|
||||
ctx.setConfirmModalOpen(true);
|
||||
} else {
|
||||
// Regular tab - use closeTab directly with skipHistory for wizard tabs
|
||||
const isWizardTab = activeTab && ctx.hasActiveWizard && ctx.hasActiveWizard(activeTab);
|
||||
const result = ctx.closeTab(
|
||||
ctx.activeSession,
|
||||
ctx.activeSession.activeTabId,
|
||||
ctx.showUnreadOnly,
|
||||
{ skipHistory: isWizardTab }
|
||||
);
|
||||
if (result) {
|
||||
ctx.setSessions((prev: Session[]) =>
|
||||
prev.map((s: Session) => (s.id === ctx.activeSession!.id ? result.session : s))
|
||||
if (closeResult.type === 'file') {
|
||||
// File tab was already closed by handleCloseCurrentTab
|
||||
trackShortcut('closeTab');
|
||||
} else if (closeResult.type === 'ai' && closeResult.tabId) {
|
||||
// AI tab - need to handle wizard confirmation
|
||||
if (closeResult.isWizardTab) {
|
||||
ctx.setConfirmModalMessage(
|
||||
'Close this wizard? Your progress will be lost and cannot be restored.'
|
||||
);
|
||||
trackShortcut('closeTab');
|
||||
ctx.setConfirmModalOnConfirm(() => () => {
|
||||
ctx.performTabClose(closeResult.tabId);
|
||||
trackShortcut('closeTab');
|
||||
});
|
||||
ctx.setConfirmModalOpen(true);
|
||||
} else {
|
||||
// Regular AI tab - close it
|
||||
const result = ctx.closeTab(
|
||||
ctx.activeSession,
|
||||
closeResult.tabId,
|
||||
ctx.showUnreadOnly,
|
||||
{ skipHistory: false }
|
||||
);
|
||||
if (result) {
|
||||
ctx.setSessions((prev: Session[]) =>
|
||||
prev.map((s: Session) => (s.id === ctx.activeSession!.id ? result.session : s))
|
||||
);
|
||||
trackShortcut('closeTab');
|
||||
}
|
||||
}
|
||||
}
|
||||
// 'prevented' or 'none' - do nothing (can't close last AI tab)
|
||||
}
|
||||
if (ctx.isTabShortcut(e, 'closeAllTabs')) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user